Compare commits

..
1 Commits
Author SHA1 Message Date
Michael Harding 3d5b72001a IE notification in Sprak Admin 2020-02-12 13:23:20 -05:00
135 changed files with 2143 additions and 4589 deletions

No files matched your search

-1
View File
@@ -34,4 +34,3 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
/build
@@ -5,7 +5,7 @@ weight = 10
# C#/.NET
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://www.nuget.org/packages/spark_sdk/) to get started.
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://nuget.org) to get started.
```
dotnet new console -lang c#
+1 -1
View File
@@ -73,7 +73,7 @@
<p>
The Spark client SDK allows you to integrate spark with your existing ecosystem. The Spark Client is built entirely on this simple, powerful API.
</p>
<a href="{{ get_url(path="@/client/_index.md") }}" class="button">Get started</a>
<a href="" class="button">Get started</a>
</div>
</div>
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+4 -23
View File
@@ -3,13 +3,11 @@ using bts.api.core;
using bts.api.orderman;
using bts.spark;
using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
@@ -21,26 +19,20 @@ namespace bts.shell {
readonly IBus _bus;
readonly OrdersModel _model;
readonly ErrorReporter _error_reporter;
readonly bool _close_on_send;
IReadOnlyDictionary<uint, SecurityMD> _md_map;
TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> _orders;
IDisposable _dispose;
[ImportingConstructor]
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter)
: this(bus, model, error_reporter, Configuration.get<bts.config.OrderTicketOptions>().close_on_send) { }
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter, bool close_on_send) {
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter) {
_bus = bus;
_model = model;
_error_reporter = error_reporter;
_close_on_send = close_on_send;
_dispose = Disposable.Create(
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketAutoLookupTag).Subscribe(_show_order_tickets),
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
);
}
@@ -62,28 +54,17 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) =>
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd,
Observable.Return(LadderSettings.Default)) {
close_on_send = _close_on_send
}
Observable.Return(LadderSettings.Default))
});
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(LadderSettings.Default.calculator_buttons)) {
close_on_send = _close_on_send
}
Observable.Return(LadderSettings.Default.calculator_buttons))
});
}
void _show_order_tickets(IImmutableSet<uint> securities) {
foreach (var id in securities.empty_if_null()) {
if (_md_map.TryGetValue(id, out var smd))
_show_order_ticket(smd);
}
}
void _show_modify_order(uint order_key) {
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
@@ -97,4 +78,4 @@ namespace bts.shell {
}
}
}
}
}
-6
View File
@@ -26,7 +26,6 @@ namespace mbts.ui {
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -46,7 +45,6 @@ namespace mbts.ui {
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
MenuOrientationGeometryKey = nameof(MenuOrientationGeometryKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
PauseGeometryKey = nameof(PauseGeometryKey),
PaymentGeometryKey = nameof(PaymentGeometryKey),
@@ -64,8 +62,6 @@ namespace mbts.ui {
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
static readonly Pen _copy_boxes_pen = new Pen(Brushes.Black, HighDpiExtensions.RoundLayoutValue(5)).frozen();
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
@@ -160,7 +156,6 @@ namespace mbts.ui {
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
@@ -184,7 +179,6 @@ namespace mbts.ui {
public static ImageSource Import => load_resource_and_freeze("Import");
public static ImageSource Key => create_icon(KeyGeometryKey);
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource MenuOrientation => create_icon(MenuOrientationGeometryKey);
public static ImageSource Pause => create_icon(PauseGeometryKey);
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
-16
View File
@@ -46,18 +46,6 @@
</sys:String>
</x:Array>
<x:Array x:Key="{x:Static mbts:Icons.CopyBoxesGeometryKey}" Type="{x:Type sys:String}">
<sys:String>
M-55,5 L-55,55 L-5,55
</sys:String>
<sys:String>
M-40,-10 L-40,40 L10,40 L10,-10 L-40,-10
</sys:String>
<sys:String>
M-10,10 L40,10 L40,-40 L-10,-40 L-10,10
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
@@ -292,10 +280,6 @@
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.MenuOrientationGeometryKey}">
M14.997001,19.999038L15.000001,23.499012C15.000001,24.327005,14.327008,25,13.500016,25L9.9970522,25 9.9970522,23.000015 13.000022,23.000015 12.997022,20.001038z M9.000001,19.000006L9.000001,30.000006 30.000001,30.000006 30.000001,19.000006z M2,2L2,23 7.000001,23 7.000001,18.500006C7.000001,17.673006,7.6730013,17.000006,8.500001,17.000006L13.000001,17.000006 13.000001,2z M1.5,0L13.500001,0C14.327001,0,15.000001,0.67300034,15.000001,1.5L15.000001,17.000006 26.956518,17.000006 23.162404,13.207182C22.772376,12.816143 22.772376,12.184205 23.162404,11.793225 23.357907,11.597736 23.613928,11.499992 23.869827,11.499992 24.125725,11.499992 24.381502,11.597736 24.576517,11.793225L25.889371,13.105454 25.866285,12.914201C25.554945,10.547287 24.699391,8.7684691 23.308905,7.6089654 21.974919,6.4954615 20.356436,6.1229606 19.066825,6.0297103L19.059648,6.0292783 20.545799,7.5160289C20.936786,7.9070184 20.936786,8.5390015 20.545799,8.929991 20.350807,9.1249857 20.094815,9.2229831 19.838825,9.2229831 19.582834,9.2229831 19.326843,9.1249857 19.131849,8.929991L15.185988,4.9830971 18.981854,1.1871986C19.371841,0.79620934 20.004819,0.79620934 20.395805,1.1871986 20.785791,1.5771885 20.785791,2.2101717 20.395805,2.601161L18.976535,4.020443 18.999199,4.0214248C20.641604,4.1119318 22.785724,4.567862 24.590892,6.0739603 26.493747,7.660778 27.606404,10.054969 27.91277,13.209869L27.913735,13.221022 29.341699,11.793225C29.536714,11.597736 29.792459,11.499992 30.048328,11.499992 30.304196,11.499992 30.560187,11.597736 30.75569,11.793225 31.146694,12.184205 31.146694,12.816143 30.75569,13.207182L26.962552,17.000006 30.500001,17.000006C31.327001,17.000006,32.000001,17.673006,32.000001,18.500006L32.000001,30.500006C32.000001,31.327005,31.327001,32.000006,30.500001,32.000006L8.500001,32.000006C7.6730013,32.000006,7.000001,31.327005,7.000001,30.500006L7.000001,25 1.5,25C0.67300034,25,0,24.327001,0,23.5L0,1.5C0,0.67300034,0.67300034,0,1.5,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
</sys:String>
@@ -48,5 +48,5 @@ namespace mbts.ui.spark {
}
}
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
}
@@ -4,10 +4,10 @@ using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -32,7 +32,6 @@ namespace mbts.spark {
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
SparkMenuDisplayVM _menu_display_vm;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
@@ -49,9 +48,7 @@ namespace mbts.spark {
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns)) {
menu_display = _menu_display_vm.safe_create()
};
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -104,8 +101,6 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
@@ -165,8 +160,7 @@ namespace mbts.spark {
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
})),
_menu_display_vm.spark_cmd
}))
};
//
_vm.selection_changed += on_selection_changed;
@@ -196,8 +190,5 @@ namespace mbts.spark {
_vm.Dispose();
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
}
}
@@ -1,7 +1,6 @@
using bts.api;
using bts.proto;
using bts.settings;
using bts.view;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -54,8 +53,6 @@ namespace bts.shell {
public readonly FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
public StartupData() { }
@@ -1,36 +0,0 @@
using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
namespace bts.shell.time_and_sales {
[ProtoContract]
class FilterStates {
[ProtoMember(1)]
public TimeFilterTuple time;
[ProtoMember(2)]
public string security;
[ProtoMember(3)]
public DoubleMinMaxState price;
[ProtoMember(4)]
public UIntMinMaxState size;
// [ProtoMember(5)]
// ProtoMember 5 cannot be used further
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public co2.AggressorSide[] aggressor_sides;
}
static class TimeAndSalesFilterStatesExtensions {
public static Func<TimeAndSalesRowVM, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<TimeAndSalesRowVM>();
pb.add_regex(fs.security, r => r.security.ToString());
pb.add_time_filter(fs.time, r => r.time);
pb.add_price(fs.price, r => r.price, r => r.security);
pb.add_numeric(fs.size, r => r.size);
pb.add_discrete_values(fs.aggressor_sides, r => r.aggressor_side);
return pb.to_predicate();
}
}
}
@@ -1,74 +0,0 @@
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System.Collections.Generic;
using System.Linq;
using static mbts.ui.controls.DIYGrid;
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
namespace bts.shell.time_and_sales {
[ProtoContract]
class TimeAndSalesSettings {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
[ProtoMember(6)]
public bool use_defaults;
public TimeAndSalesSettings() { }
public TimeAndSalesSettings(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public TimeAndSalesSettings(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static TimeAndSalesSettings Default(SecurityCriteria security_criteria) =>
new TimeAndSalesSettings(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public TimeAndSalesSettings with(SecurityCriteria security_criteria) =>
new TimeAndSalesSettings(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.col_id(), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(Columns.Time.col_id(), DefaultColumnHeaders.Time, width: DefaultColumnWidths.TimeOfDay);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.col_id(), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(Columns.Size.col_id(), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(Columns.Aggressor.col_id(), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.col_id(), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return security_key;
}
}
}
}
}
@@ -1,26 +1,27 @@
using bts.api;
using bts.co2;
using mbts;
using mbts.co2;
using mbts.spark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace bts.shell.time_and_sales {
namespace mbts.spark {
class TimeAndSalesModel : IDisposable {
internal readonly SecurityCriteria security_criteria;
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
IDisposable _dispose;
public TimeAndSalesModel(IBus bus, MDConsumer consumer, IObservable<SecurityCriteria> security_criteria) {
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
this.security_criteria = security_criteria;
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
var copy = curr_channels;
return copy.Values.SelectMany(c => c.read_trades()).ToList();
}));
_dispose = security_criteria.SelectManyInSeries(sc =>sc.subscribe_md_map(bus)).Subscribe(
_dispose = security_criteria.subscribe_md_map(bus).Subscribe(
md_map => {
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
@@ -94,4 +95,4 @@ namespace bts.shell.time_and_sales {
}
}
}
}
}
@@ -1,70 +0,0 @@
using bts.api;
using bts.co2;
using bts.utils;
using bts.utils.csv;
using mbts;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
namespace bts.shell.time_and_sales {
class TimeAndSalesRowVM : ViewModelBase, DIYGrid.IRowVM, ISecurityRow, IEquatable<TimeAndSalesRowVM> {
// key comparer guarentees deterministic sorting
// if the rows in the collection have unique row_keys.
public static readonly IComparer<TimeAndSalesRowVM> key_comparer =
new ComparerBuilder<TimeAndSalesRowVM>(new[] {
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
})
.to_comparer()
.to_safe();
readonly TradeMessageImmutable _trade;
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
this.row_key = row_key;
this.security = security;
_trade = trade;
trade_key = trade.Key;
}
public event Action invalidated;
public int row_key { get; }
public uint trade_key { get; }
public uint security_key => security.Id;
public SecurityImmutable security { get; }
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
public double price => _trade.price();
public uint size => _trade.Size;
public AggressorSide aggressor_side => _trade.AggressorSide;
public void raise_invalidated() =>
invalidated?.Invoke();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesRowVM),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public bool Equals(TimeAndSalesRowVM other) =>
other != null &&
row_key == other.row_key;
public override bool Equals(object obj) =>
Equals(obj as TimeAndSalesRowVM);
public override int GetHashCode() =>
row_key;
}
}
@@ -1,363 +1,86 @@
using bts.api;
using bts.co2;
using bts.collections;
using bts.utils;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Windows.Controls;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace bts.shell.time_and_sales {
class TimeAndSalesVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection {
const int _max_row_count = 1000;
static int _id_counter;
static IReadOnlyList<Func<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>();
namespace mbts.spark {
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
readonly IBus _bus;
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
readonly ColumnSortManager _sort_manager;
readonly RowFactory _row_factory = new RowFactory();
readonly LoggerContext _log;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly DeferrableActionWrapper
_refresh_filter,
_refresh_sort,
_refresh_selected_rows;
IComparer<TimeAndSalesRowVM> _comparer_backing;
Func<TimeAndSalesRowVM, bool> _filter_backing;
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
ImmutableList<TimeAndSalesRowVM> _all_rows = ImmutableList<TimeAndSalesRowVM>.Empty;
ImmutableList<TradeInfo> _buffered_trades;
/// Although the sorting criteria can be obtained from the <see cref="_sort_manager"/> (<see cref="ColumnSortManager.SortingCriteria"/>),
/// a local copy is kept because the settings collector may call <see cref="create"/> from a thread
/// other than the dispatcher thread associated with this instance as a viewmodel,
/// and the <see cref="ColumnSortManager.SortingCriteria"/> property is not thread-safe.
/// Therefore, <see cref="create"/> returns this local copy, and thus avoids accessing the non-thread-safe property.
ImmutableList<SortingCriteria> _sorting_criteria = ImmutableList<SortingCriteria>.Empty;
TimeAndSalesRowVM _current_row;
bool _is_paused;
int
_load_settings_count,
_filter_change_count,
_sort_change_count;
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
_model = model;
_settings_manager = settings_manager;
// deferrable filter/sort refresh
_refresh_filter = new DeferrableActionWrapper(_refresh_filter_impl);
_refresh_sort = new DeferrableActionWrapper(_refresh_sort_impl);
_refresh_selected_rows = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke(_selected_rows));
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<TimeAndSalesRowVM>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
grid_settings_manager.load_from(settings_manager.current_settings());
// Columns
add_column(
TimeAndSalesView.Columns.Security,
r => r.security.ToString(),
DefaultColumnHeaders.Security,
DefaultColumnWidths.Security,
security_filter,
DataGridFilterKnownTemplates.Regex);
add_column(
TimeAndSalesView.Columns.Time,
r => r.time.ToString("h:mm:ss tt"),
DefaultColumnHeaders.Time,
DefaultColumnWidths.Time,
time_filter,
DataGridFilterKnownTemplates.TimeFilter);
add_column(
TimeAndSalesView.Columns.Price,
r => r.security.spec_or_default().format(r.price),
DefaultColumnHeaders.Price,
DefaultColumnWidths.Price,
price_filter,
DataGridFilterKnownTemplates.DoubleMinMax,
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Size,
r => r.size.ToString(),
DefaultColumnHeaders.Size,
DefaultColumnWidths.Size,
size_filter,
DataGridFilterKnownTemplates.UIntMinMax,
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Aggressor,
r => r.aggressor_side.ToString(),
DefaultColumnHeaders.Aggressor,
DefaultColumnWidths.Aggressor,
aggressor_filter,
DataGridFilterKnownTemplates.EnumValues);
add_column(
TimeAndSalesView.Columns.SecurityKey,
r => r.security_key.ToString(),
DefaultColumnHeaders.SecurityKey,
DefaultColumnWidths.SecurityKey,
security_key_filter,
DataGridFilterKnownTemplates.Regex);
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager);
// commands
show_ladder_cmd = new DelegateCommand(() => {
if (current_row != null)
request_show_ladder?.Invoke(current_row);
});
// init IDisposable resources
var resources = new List<IDisposable>();
resources.AddRange(_filters.Select(f => {
f.filter_changed += _refresh_filter.execute;
return Disposable.Create(() => f.filter_changed -= _refresh_filter.execute);
}));
_sort_manager.active_sorting_changed += _refresh_sort.execute;
resources.Add(Disposable.Create(() => _sort_manager.active_sorting_changed -= _refresh_sort.execute));
_dispose = Disposable.Create(resources.ToArray());
// local methods
void add_column(
TimeAndSalesView.Columns col_key,
Func<TimeAndSalesRowVM, string> get_cell_text,
string header,
double width,
IDataGridColumnFilter filter,
string filter_template,
System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<TimeAndSalesRowVM, TimeAndSalesView.Columns>(
col_key, get_cell_text, filter, filter_template, _get_aggressor_brush) {
id = col_key.col_id(), Header = header, Width = width, horizontal_alignment = h_alignment
}
);
}
public event Action<ImmutableHashSet<TimeAndSalesRowVM>> selected_rows_changed;
public event Action<TimeAndSalesRowVM> current_row_changed, request_show_ladder;
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public TimeAndSalesRowVM current_row {
get => _current_row;
set {
if (Set(ref _current_row, value))
current_row_changed?.Invoke(value);
}
}
public ObservableCollection<TimeAndSalesRowVM> rows { get; } = new ObservableCollection<TimeAndSalesRowVM>();
public ImmutableHashSet<TimeAndSalesRowVM> selected_rows => _selected_rows;
public int total_row_count => _all_rows.Count;
public SimpleGridSettingsManager<TimeAndSalesRowVM> grid_settings_manager { get; }
public bool is_paused {
get => _is_paused;
set {
if (_is_paused != value) {
_is_paused = value;
if (value) {
_buffered_trades = ImmutableList<TradeInfo>.Empty;
} else {
var resume = _buffered_trades;
_buffered_trades = null;
on_new_trades(resume);
}
}
}
}
// filters
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<TimeAndSalesRowVM>(r => r.time);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security.ToString(), nameof(TimeAndSalesRowVM.security));
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<TimeAndSalesRowVM>(r => r.price);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, UIntMinMaxState> size_filter { get; } = new UIntMinMax<TimeAndSalesRowVM>(r => r.size, nameof(TimeAndSalesRowVM.size));
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<TimeAndSalesRowVM, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_key_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security_key.ToString());
// commands
public ICommand show_ladder_cmd { get; }
IComparer<TimeAndSalesRowVM> _comparer {
get => _comparer_backing;
set {
if (value == null) {
_log.warn("Comparer should not be set to null, will use the default comparer instead (based on trade time, then row key).");
_comparer_backing = new ComparerBuilder<TimeAndSalesRowVM>(new[] {
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
})
.to_comparer()
.to_safe();
} else {
_comparer_backing = value;
}
var sorted_rows = rows.ToArray();
var snapshot = _selected_rows;
Array.Sort(sorted_rows, _comparer_backing);
using (_refresh_selected_rows.defer_execution()) {
for (int i = 0; i < sorted_rows.Length; i++) {
if (rows[i].row_key != sorted_rows[i].row_key)
rows[i] = sorted_rows[i];
}
}
}
}
Func<TimeAndSalesRowVM, bool> _filter {
get => _filter_backing;
set {
var old_filter = _filter_backing;
var new_filter = value;
_filter_backing = new_filter;
if (new_filter != null) {
for (int i = 0; i < rows.Count; i++) {
if (!new_filter(rows[i]))
rows.RemoveAt(i);
}
rows.RemoveWhere(r => !new_filter(r));
if (old_filter != null) {
foreach (var r in _all_rows) {
if (!old_filter(r) && new_filter(r))
rows.InsertSorted(r, _comparer);
}
}
} else if (old_filter != null) {
foreach (var r in _all_rows) {
if (!old_filter(r))
rows.InsertSorted(r, _comparer);
}
}
}
}
IEnumerable<IDataGridColumnFilter<TimeAndSalesRowVM>> _filters =>
_filter_getters.Select(fn => fn(this));
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting_criteria, GridSettings grid_settings) create() =>
(_get_filter_states(), _sorting_criteria, grid_settings_manager.create());
public void load(FilterStates filters, IEnumerable<SortingCriteria> sorting_criteria, GridSettings grid_settings) {
using (_refresh_filter.defer_execution())
using (_refresh_sort.defer_execution()) {
_log.info($"begin load settings ({++_load_settings_count})...");
var timer = new Stopwatch();
timer.Start();
grid_settings_manager.load_from(grid_settings);
_sort_manager.SortingCriteria = sorting_criteria;
// load filters
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
if (startup_data.filters is FilterStates filters) {
time_filter.load_state(filters.time);
security_filter.load_state(filters.security);
price_filter.load_state(filters.price);
size_filter.load_state(filters.size);
aggressor_filter.load_state(filters.aggressor_sides);
security_key_filter.load_state(filters.security_key);
//
timer.Stop();
_log.info($"load settings ({_load_settings_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
}
// Columns
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public void on_new_trades(IReadOnlyList<TradeInfo> trades) {
/// The total row count is limited by <see cref="_max_row_count"/>;
/// trades are kept in a circular buffer (FIFO).
/// If paused, just need to add trades to <see cref="_buffered_trades"/>.
if (is_paused) {
if (_buffered_trades == null)
_buffered_trades = ImmutableList<TradeInfo>.Empty;
if (trades.Count >= _max_row_count) {
_buffered_trades = _buffered_trades
.Clear()
.AddRange(ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count));
} else {
int overflow_count = _buffered_trades.Count + trades.Count - _max_row_count;
if (overflow_count > 0) {
var rows_to_remove = _buffered_trades.WrapSubRange(0, overflow_count);
_buffered_trades = _buffered_trades.RemoveRange(0, overflow_count);
}
_buffered_trades = _buffered_trades.AddRange(trades);
}
return;
}
/// If not paused, update in the normal fasion:
/// add to <see cref="_all_rows"/> and <see cref="rows"/>
if (trades.Count >= _max_row_count) {
trades = ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count);
_all_rows = _all_rows.Clear();
rows.Clear();
} else {
int overflow_count = _all_rows.Count + trades.Count - _max_row_count;
if (overflow_count > 0) {
var rows_to_remove = _all_rows.WrapSubRange(0, overflow_count);
_all_rows = _all_rows.RemoveRange(0, overflow_count);
rows.RemoveSorted(rows_to_remove, _comparer);
}
}
var all_rows_bd = _all_rows.ToBuilder();
foreach (var t in trades) {
var r = _row_factory.create(t.security, t.trade);
all_rows_bd.Add(r);
if (_filter_backing == null || _filter_backing(r))
rows.InsertSorted(r, _comparer);
}
_all_rows = all_rows_bd.ToImmutable();
RaisePropertyChanged(nameof(total_row_count));
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
try {
if (e.Column is ISortableDIYGridColumn sc) {
_log.info($"handling sort event for column {sc.id}.");
_sort_manager.OnColumnClick(sc, KeyboardExtensions.IsShiftDown());
var sorted_rows = rows.ToArray();
var snapshot = _selected_rows;
_refresh_sort_impl();
} else {
_log.warn($"ignoring sort event because column '{e.Column?.id}' does not implement {nameof(ISortableDIYGridColumn)}.");
}
} catch (Exception ex) {
_log.error($"error handling sort event: {ex}.");
}
}
public StartupData create() =>
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
public void row_selection_changed(SelectionChangedEventArgs e) {
var bd = _selected_rows.ToBuilder();
if (e.AddedItems != null)
bd.AddRange(e.AddedItems.OfType<TimeAndSalesRowVM>());
if (e.RemovedItems != null)
bd.removeRange(e.RemovedItems.OfType<TimeAndSalesRowVM>());
_selected_rows = bd.ToImmutable();
_refresh_selected_rows.execute();
}
static IComparer<TimeAndSalesRowVM> _get_column_comparer(string column_name) =>
_get_column_comparer(EnumHelper.parse<TimeAndSalesView.Columns>(column_name));
static IComparer<TimeAndSalesRowVM> _get_column_comparer(TimeAndSalesView.Columns column) {
switch (column) {
case TimeAndSalesView.Columns.Aggressor: return wrap(r => r.aggressor_side);
case TimeAndSalesView.Columns.Time: return wrap(r => r.time);
case TimeAndSalesView.Columns.Security: return wrap(r => r.security);
case TimeAndSalesView.Columns.Price: return wrap(r => r.price);
case TimeAndSalesView.Columns.Size: return wrap(r => r.size);
case TimeAndSalesView.Columns.SecurityKey: return wrap(r => r.security_key);
case TimeAndSalesView.Columns.RowKey: return wrap(r => r.row_key);
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
}
IComparer<TimeAndSalesRowVM> wrap<T>(Func<TimeAndSalesRowVM, T> selector) =>
Comparer<T>.Default.wrap(selector);
}
Brush _get_aggressor_brush(TimeAndSalesRowVM r) {
private Brush _get_aggressor_brush(Row r) {
BTSColor c;
Brush b;
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
@@ -367,6 +90,14 @@ namespace bts.shell.time_and_sales {
return b;
}
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
FilterStates _get_filter_states() =>
new FilterStates {
time = time_filter.get_state(),
@@ -376,35 +107,73 @@ namespace bts.shell.time_and_sales {
security_key = security_key_filter.get_state(),
aggressor_sides = aggressor_filter.get_state()
};
#endregion
void _refresh_sort_impl() {
_log.info($"begin change sort ({++_sort_change_count})...");
var timer = new Stopwatch();
timer.Start();
_comparer = _sort_manager.BuildComparer(_get_column_comparer, TimeAndSalesRowVM.key_comparer);
_sorting_criteria = ImmutableList.CreateRange(_sort_manager.SortingCriteria);
timer.Stop();
_log.info($"sort change ({_sort_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
protected override IEnumerable<SortingCriteria> get_default_sorting() {
yield return new SortingCriteria(nameof(Row.time), false);
}
void _refresh_filter_impl() {
_log.info($"begin change filter ({++_filter_change_count})...");
var timer = new Stopwatch();
timer.Start();
_filter = _get_filter_states().build_predicate();
timer.Stop();
_log.info($"filter change ({_filter_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
protected override Row row_factory(TradeInfo row_model) =>
new Row(this, row_model.security, row_model.trade);
async Task _show_ladder_async(Row row) {
var smd_lookup = await SparkUtils.get_security_md_async(_bus, row.security.Id);
if (smd_lookup.success) _bus.post(SparkTags.ShowLadderTag, smd_lookup.result);
}
class RowFactory {
// the row key must be unique to each row in the collection
int _row_id_counter;
public class Row : SecurityRow, DIYGrid.IRowVM {
readonly uint _trade_key;
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
readonly string
_security_text,
_time_text,
_price_text,
_size_text,
_aggressor_text,
_trade_key_text,
_security_key_text;
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
_trade_key = trade.Key;
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
price = trade.price();
size = trade.Size;
aggressor_side = trade.AggressorSide;
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
_security_text = security.ToString();
_time_text = time.ToString("M/d h:mm:ss tt");
_price_text = security.ContractSpec.format(price);
_size_text = size.ToString();
_aggressor_text = aggressor_side.ToString();
_trade_key_text = _trade_key.ToString();
_security_key_text = security_key.ToString();
}
public event Action invalidated;
public uint trade_key => _trade_key;
public DateTime time { get; }
public double price { get; }
public uint size { get; }
public AggressorSide aggressor_side { get; }
public ICommand show_ladder { get; }
public string this[TimeAndSalesView.Columns col] {
get {
switch (col) {
case TimeAndSalesView.Columns.Security: return _security_text;
case TimeAndSalesView.Columns.Time: return _time_text;
case TimeAndSalesView.Columns.Price: return _price_text;
case TimeAndSalesView.Columns.Size: return _size_text;
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
default: return null;
}
}
}
public void raise_invalidated() => invalidated?.Invoke();
}
}
}
}
@@ -1,10 +1,10 @@
<UserControl
x:Class="bts.shell.time_and_sales.TimeAndSalesView"
x:Class="mbts.ui.spark.TimeAndSalesView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:tas="clr-namespace:bts.shell.time_and_sales"
xmlns:usp="clr-namespace:mbts.ui.spark"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -26,14 +26,13 @@
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
Rows="{Binding rows}"
Rows="{Binding rows_view}"
CurrentRow="{Binding current_row}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding show_ladder_cmd}"/>
<KeyBinding Key="Return" Command="{Binding show_ladder_cmd}"/>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{Binding select_all_cmd}"/>
<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
@@ -44,18 +43,13 @@
</c:DIYGrid.CommandBindings>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<tas:TimeAndSalesRowView/>
<usp:TimeAndSalesRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Trades:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding total_row_count, Mode=OneWay}" />
</TextBlock>
<TextBlock Text=" "/>
</StatusBarItem>
</StatusBar>
</Grid>
@@ -1,21 +1,17 @@
using mbts.ui.controls;
using mbts.spark;
using mbts.ui.controls;
using System.Windows.Controls;
namespace bts.shell.time_and_sales {
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for TimeAndSalesView.xaml
/// </summary>
partial class TimeAndSalesView : UserControl {
public enum Columns { Time, Security, Price, Size, SecurityKey, Aggressor, RowKey }
public enum Columns { Time, Security, Price, Size, OrderType, SecurityKey, Aggressor }
public TimeAndSalesView() =>
InitializeComponent();
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesRowVM> { }
static class TimeAndSalesViewColumnsExtensions {
public static string col_id(this TimeAndSalesView.Columns column) =>
column.ToString();
}
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
}
@@ -1,218 +1,125 @@
using bts.api;
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
namespace bts.shell.time_and_sales {
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly bool _can_pause_updates;
TimeAndSalesVM _vm;
SingleValuePump<TimeAndSalesVM> _vm;
TimeAndSalesModel _model;
SecurityCriteria _security_criteria_backing;
TimeAndSalesSettings _state;
StartupData _state;
TimeAndSalesView _view;
SparkCommand[] _commands;
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, TimeAndSalesSettings state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager, bool can_pause_updates)
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
Log.assert(state != null);
_consumer = consumer;
_factory = factory;
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
if (_state.security_criteria == null)
_state = _state.with(new SecurityCriteria());
_security_criteria_backing = _state.security_criteria;
_state = state;
_settings_manager = settings_manager;
_can_pause_updates = can_pause_updates;
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().time_and_sales ? _window_type : null;
Icon.Freeze();
}
event Action<SecurityCriteria> _security_criteria_changed;
public override string window_type => _window_type;
public override IEnumerable<SparkCommand> Commands => _commands;
public override UIElement Content => _view;
public override double MinWidth => TimeAndSalesSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
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 override TimeAndSalesSettings SaveState {
get {
_ensure_state(true);
return _state;
}
}
SecurityCriteria _security_criteria {
get => _security_criteria_backing;
set {
_security_criteria_backing = value;
_state = null;
_security_criteria_changed?.Invoke(value);
}
}
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
_dispose_resources();
public void thread_init() {
_dispose_resources();
_ensure_state();
if (!_state.use_defaults)
_settings_manager.instance_settings = _state.grid;
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new TimeAndSalesVM(_settings_manager);
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
_vm.selected_rows_changed += o.OnNext;
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
});
var current_row_src = Observable.Create<TimeAndSalesRowVM>(o => {
if (_vm.current_row != null)
o.OnNext(_vm.current_row);
_vm.current_row_changed += o.OnNext;
return Disposable.Create(() => _vm.current_row_changed -= o.OnNext);
});
var current_sec_key_src = current_row_src.Select(r => {
var result = ImmutableHashSet<uint>.Empty;
if (r != null)
result = result.Add(r.security_key);
return result;
});
_vm = new SingleValuePump<TimeAndSalesVM>();
var connectable_builder = new ConnectableSeriesBuilder<TimeAndSalesVM>(_vm.observable);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
SparkCommand toggle_is_paused_cmd = null;
toggle_is_paused_cmd = new SparkCommandCommand(get_is_paused_cmd_title(), Icons.Pause, new DelegateCommand(() => {
_vm.is_paused = !_vm.is_paused;
toggle_is_paused_cmd.title = get_is_paused_cmd_title();
})) {
show_in_toolbar = _can_pause_updates,
show_in_context_menu = _can_pause_updates
};
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
_commands = new SparkCommand[] {
// csv export
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
// ladder
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
// ticket
SparkCommands.ShowOrderTicketAutoLookup(SparkCommands.KeyGestures.ShowOrderTicket)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
// fills
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills)
.complete(selected_rows_src, always_true, get_fills_startup_data),
// mview
SparkCommands.ShowMarketView
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
// pause
toggle_is_paused_cmd,
// change securities
SparkCommands.change_securities_command(() => (update_securities_cmd, _security_criteria)),
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
s.with(_vm.value.columns.Select(c => c.create()))
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
})),
_menu_display_vm.spark_cmd
}))
};
_vm.load(_state.filters, _state.sorting_criterion, _state.grid);
_vm.request_show_ladder += show_ladder;
var security_criteria_src = _bus.subscribe(update_securities_cmd.tag);
var subs = new List<IDisposable> {
security_criteria_src.Subscribe(sc => _security_criteria = sc),
Disposable.Create(() => _vm.request_show_ladder -= show_ladder)
};
_model = new TimeAndSalesModel(_bus, _consumer, Observable.Create<SecurityCriteria>(o => {
o.OnNext(_security_criteria);
return security_criteria_src.Subscribe(o);
}));
subs.Add(_model.trades.OnDispatcher().Subscribe(_vm.on_new_trades));
_subscriptions = Disposable.Create(subs.ToArray());
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm;
// local methods
_subscriptions = Disposable.Create(
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
connectable_builder.connect_all());
_initialize();
bool always_true<T>(T _) => true;
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsSettings(new FillsFilterStates {
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
});
string get_is_paused_cmd_title() =>
$"{(_vm.is_paused ? "Resume" : "Pause")} Updates";
void show_ladder(TimeAndSalesRowVM row) {
if (row != null)
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet.Create(new[] { row.security_key }));
void update_criteria(SecurityCriteria c) {
_state = _vm.value.create().with(c);
_initialize();
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
void _dispose_resources() {
Disposable.dispose(ref _subscriptions);
Disposable.dispose(ref _vm);
Disposable.dispose(ref _model);
if (_commands != null) {
_commands.OfType<IDisposable>().DisposeAll();
_commands = null;
}
Disposable.dispose(ref _model);
Disposable.dispose(ref _vm);
}
void _ensure_state(bool force_recreate = false) {
if (_state == null || force_recreate) {
if (_vm == null) {
_state = TimeAndSalesSettings.Default(_security_criteria);
} else {
var (filters, sorting, grid) = _vm.create();
_state = new TimeAndSalesSettings(_security_criteria, filters, grid, sorting.ToList()) {
use_defaults = _settings_manager.instance_settings == null
};
}
// should be safe to assume _menu_display_vm and _vm will either both be null, or both be non null,
// but redundant null checks don't hurt, and just may prevent a crash.
if (_menu_display_vm != null)
_state.menu_display = _menu_display_vm.safe_create();
}
void _initialize() {
Disposable.dispose(ref _model);
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm.value;
}
}
}
}
@@ -1,32 +1,33 @@
using bts.api;
using bts.spark;
using bts.shell.time_and_sales;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
using mbts.config;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.TimeAndSalesView;
namespace bts.shell {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesSettings> {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
readonly IBus _bus;
readonly MDConsumer _consumer;
readonly UserSettingsHub _settings_hub;
readonly TimeAndSalesConfig _time_and_sales_conf;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
_bus = bus;
_consumer = consumer;
_settings_hub = settings_hub;
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
@@ -35,21 +36,91 @@ namespace bts.shell {
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
public override Task<IShellWindow> load(StartupData state) =>
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
_time_and_sales_conf.can_pause_updates));
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
public async void show(SecurityCriteria criteria) {
var w = await load(new TimeAndSalesSettings(criteria));
var w = await load(new StartupData(criteria));
_bus.post(SparkTags.ShowWindow, w);
}
readonly struct TimeAndSalesConfig {
public readonly bool can_pause_updates;
[ProtoContract]
public class StartupData {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public TimeAndSalesConfig(bts.config.TimeAndSalesOptions options) =>
can_pause_updates = options.can_pause_updates;
public StartupData() { }
public StartupData(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public StartupData with(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible:false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return security_key;
}
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
public TimeFilterTuple time;
[ProtoMember(2)]
public string security;
[ProtoMember(3)]
public DoubleMinMaxState price;
[ProtoMember(4)]
public UIntMinMaxState size;
// [ProtoMember(5)]
// ProtoMember 5 cannot be used further
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public co2.AggressorSide[] aggressor_sides;
}
}
}
}
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
}
@@ -3,7 +3,6 @@ using bts.api;
using bts.shell;
using bts.shell.spreader;
using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -28,7 +27,6 @@ namespace mbts.spark {
StartupData _state;
OrdersModel _orders_model;
SyntheticManagementView _view;
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
IReadOnlyList<SparkCommand> _commands;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -45,7 +43,7 @@ namespace mbts.spark {
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().with(menu_display: _menu_display_vm.safe_create());
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -55,8 +53,6 @@ namespace mbts.spark {
public void thread_init() {
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
@@ -101,8 +97,7 @@ namespace mbts.spark {
new SparkCommandCommand(
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
show_in_toolbar = false
},
_menu_display_vm.spark_cmd
}
};
_subscriptions = Disposable.Create(
@@ -134,9 +129,6 @@ namespace mbts.spark {
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
struct RowModificationParams {
public readonly SecurityImmutable security;
public readonly bool has_working_orders;
@@ -48,28 +48,14 @@ namespace bts.shell {
public readonly FilterStates filters;
[ProtoMember(3)]
public readonly GridSettings grid;
[ProtoMember(4)]
public readonly SparkMenuDisplaySettings menu_display;
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default)
: this(sorting_criteria, filters, grid, SparkMenuDisplaySettings.Default) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid, SparkMenuDisplaySettings menu_display) {
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
this.sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
this.menu_display = menu_display;
}
public StartupData with(List<SortingCriteria> sorting_criteria = null, FilterStates filters = null, GridSettings grid = null, SparkMenuDisplaySettings? menu_display = null) =>
new StartupData(
sorting_criteria ?? this.sorting_criteria,
filters ?? this.filters,
grid ?? this.grid,
menu_display ?? this.menu_display);
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
static class DefaultColumns {
@@ -25,7 +25,6 @@
<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.LP1_RP1" Left="{StaticResource HorizontalPush1}" Right="{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}"/>
@@ -131,6 +130,22 @@
</ResourceDictionary>
</UserControl.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.EditPane}}">
<VisualState Name="{x:Static u:VisualStates.Active}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static u:VisualStates.Inactive}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<AdornerDecorator>
<Grid>
@@ -138,19 +153,13 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox
x:Name="_tree_view"
Grid.RowSpan="2"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinWidth="180"
Background="Transparent"
BorderThickness="0,0,1,0"
SelectedItem="{Binding selected_definition}"
>
<ListBox.ItemTemplate>
@@ -164,176 +173,275 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid x:Name="edit_pane" Grid.Column="1">
<u:StateManager.VisualState>
<Binding Path="selected_definition">
<Binding.Converter>
<cv:ValueMap Default="{x:Static u:VisualStates.Active}">
<cv:KeyValue Key="{x:Null}" Value="{x:Static u:VisualStates.Inactive}"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState Name="{x:Static u:VisualStates.Active}"/>
<VisualState Name="{x:Static u:VisualStates.Inactive}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="algo_editor_scroll_viewer" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(FrameworkElement.ToolTip).(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ToolTip>
<ToolTip Visibility="Collapsed">
<StackPanel Orientation="Vertical">
<TextBlock FontStyle="Italic">Select an existing algo to edit from the list to the left.</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock FontStyle="Italic">Or click the</TextBlock>
<Image Source="{x:Static u:Icons.AddBoxes}" Height="20" Margin="2,0"/>
<TextBlock FontStyle="Italic"> button to create a new one.</TextBlock>
</StackPanel>
</StackPanel>
</ToolTip>
</Grid.ToolTip>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}" UseLayoutRounding="True"/>
<Button
x:Name="custom_badge_color_picker_button"
Background="{Binding selected_definition.badge_fill}"
Margin="{StaticResource Margin.LP1}"
ToolTip="Change the icon color used for this algo."
IsEnabled="True"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox
x:Name="default_badge_color_checkbox"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="6,4,0,4"
IsChecked="{Binding selected_definition.custom_badge_color.enabled, Converter={StaticResource NotConverter}}"
>
<CheckBox.ToolTip>
<TextBlock>
<Run>Use the default icon color for this algo.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: the default color is that of the algo-root.</Run>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid u:StateManager.VisualStateCollection="{Binding content_visual_states}" Margin="{StaticResource Margin.TP1}" >
<VisualStateManager.VisualStateGroups>
<!--Name-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameDefinition, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definitions."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameDefinitionWhileTemplatesPresent, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definition while templates present."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameTemplate, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename templates."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!--Abbreviation-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<VisualState Name="{x:Static Populated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Unpopulated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static NotSupportedForTemplates, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(ToolTipService.ShowOnDisabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_not_supported_for_templates_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!--Badge Selector-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="default_badge" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="0.5"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="default_badge_radio_button" Storyboard.TargetProperty="(ToggleButton.IsChecked)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Name:</TextBlock>
<TextBox
x:Name="name_text_box"
Grid.Column="1"
ToolTipService.ShowOnDisabled="True"
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
/>
<Grid x:Name="abbreviation_root" Grid.Column="2">
<Grid.ToolTip>
<Grid>
<TextBlock x:Name="abbreviation_active_tooltip" Visibility="Collapsed">
<TextBlock.Inlines>
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
</TextBlock.Inlines>
</TextBlock>
</CheckBox.ToolTip>
<Grid x:Name="default_badge_content_root">
<TextBlock x:Name="abbreviation_not_supported_for_templates_tooltip" Visibility="Collapsed">
Not supported for templates.
</TextBlock>
</Grid>
</Grid.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
</Grid>
</Grid>
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Icon:</TextBlock>
<RadioButton
x:Name="default_badge_radio_button"
Grid.Column="1"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP1}"
GroupName="Badge"
>
<RadioButton.ToolTip>
<ToolTip DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Vertical">
<TextBlock>Use the default icon color for this algo.</TextBlock>
<StackPanel TextElement.FontStyle="Italic" Orientation="Horizontal">
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
<usp:OrderTypeItem
Margin="2,0"
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
DisplayName="{Binding selected_definition.algo_root.display_name}"
/>
<TextBlock>).</TextBlock>
</StackPanel>
</StackPanel>
</ToolTip>
</RadioButton.ToolTip>
<Grid x:Name="default_badge_content_root">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Default</TextBlock>
<Rectangle
x:Name="default_badge"
Grid.Column="1"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
</Grid>
</RadioButton>
<RadioButton
Grid.Column="2"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP2}"
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
GroupName="Badge"
>
<RadioButton.ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Use Default Icon</TextBlock>
<TextBlock Grid.ColumnSpan="3">Specify a custom icon color for this algo.</TextBlock>
<TextBlock Grid.Row="1" FontStyle="Italic">Hint: Click</TextBlock>
<Rectangle
x:Name="default_badge"
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Margin="3,0"
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
</Grid>
</CheckBox>
<cp:ColorPickerControl
Grid.Row="1"
ToolTipService.ShowOnDisabled="True"
Margin="{StaticResource Margin.TP1}"
Background="Transparent"
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
PreviewMouseDown="_on_badge_color_picker_activated"
PreviewGotKeyboardFocus="_on_badge_color_picker_activated"
/>
</Grid>
</Border>
</Popup>
<TextBox
x:Name="name_text_box"
Grid.Column="1"
u:TextBoxBehavior.TextPrompt="Name"
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
MinWidth="50"
IsEnabled="True"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
ToolTip="This algo's full name."
/>
<TextBox
x:Name="abbreviation_text_box"
Grid.Column="2"
u:TextBoxBehavior.TextPrompt="Abbrev."
VerticalAlignment="Center"
Margin="{StaticResource Margin.LP1_RP1}"
MinWidth="50"
IsEnabled="True"
Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"
>
<TextBox.ToolTip>
<TextBlock>
<TextBlock.Inlines>
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
</TextBlock.Inlines>
</TextBlock>
</TextBox.ToolTip>
</TextBox>
<ScrollViewer x:Name="algo_editor_scroll_viewer" Grid.Row="1" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto">
</RadioButton.ToolTip>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Custom</TextBlock>
<Button
x:Name="custom_badge_color_picker_button"
Grid.Column="1"
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Margin="{StaticResource Margin.LP1}"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
<cp:ColorPickerControl
Background="Transparent"
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
/>
</Border>
</Popup>
</Grid>
</RadioButton>
</Grid>
<usp:AlgoEditorView
Grid.Row="2"
DataContext="{Binding selected_definition}"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
/>
</ScrollViewer>
</Grid>
<c:DialogSkirt
Grid.Row="2"
Grid.Column="1"
BorderThickness="0"
>
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
Save Algo
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
</StackPanel.Margin>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding revert_cmd_tooltip}">
Revert Algo
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -19,17 +19,6 @@ namespace mbts.ui.spark {
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
}
void _on_badge_color_picker_activated(object sender, MouseButtonEventArgs e) =>
_on_badge_color_picker_activated();
void _on_badge_color_picker_activated(object sender, KeyboardFocusChangedEventArgs e) =>
_on_badge_color_picker_activated();
void _on_badge_color_picker_activated() {
if (DataContext is AlgoStudioVM vm && vm.selected_definition != null)
vm.selected_definition.custom_badge_color.enabled = true;
}
public static class VisualStateNames {
public static class EditPane {
public const string GroupName = nameof(EditPane);
@@ -55,7 +44,8 @@ namespace mbts.ui.spark {
public static readonly string
Populated = $"{GroupName}{nameof(Populated)}",
Unpopulated = $"{GroupName}{nameof(Unpopulated)}";
Unpopulated = $"{GroupName}{nameof(Unpopulated)}",
NotSupportedForTemplates = $"{GroupName}{nameof(NotSupportedForTemplates)}";
}
public static class BadgeSelector {
@@ -9,7 +9,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -69,12 +68,23 @@ namespace mbts.spark {
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.CopyBoxes, _vm.clone_selected_definition_cmd));
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
show_in_toolbar = _ui_configuration.can_save_specific
});
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
// clone definition
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
_commands.Add(clone_selected_definition_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
// save
if (_ui_configuration.can_save_specific) {
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_definition is AlgoDefinitionVM d)
_vm.save_definition(d).ignore();
}, content_is_dirty_src));
}
// save all
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
@@ -107,11 +117,30 @@ namespace mbts.spark {
_resources.Add(can_redo_source.Subscribe(redo_cmd));
}
// remove
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
vm => vm != null,
out var remove_cmd_observer)));
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
if (value is AlgoDefinitionVM d) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
prompt = null;
icon = default;
}
if (prompt != null) {
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
switch (resp) {
case BTSMessageBoxButtons.Yes: break;
case BTSMessageBoxButtons.No:
default: return;
}
}
_vm.remove_definition(d).ignore();
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
}
void _dispose_core() {
@@ -120,4 +149,4 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
}
}
}
}
@@ -6,6 +6,7 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
@@ -15,6 +16,7 @@ namespace mbts.spark {
readonly AdminModel _admin_model;
readonly ErrorReporter _error_reporter;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
IDisposable _dispose;
[ImportingConstructor]
@@ -32,13 +34,17 @@ namespace mbts.spark {
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
public IAlgoDefinitionMetadataService metadata_service { get; }
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving definition");
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
return null;
@@ -47,22 +53,23 @@ namespace mbts.spark {
public async Task<bool> remove_definition(AlgoKey definition_key) {
var req = new AlgoDefinitionRemoveRequest();
_set_key(ref req, definition_key);
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(
() => _service.remove_algo_definition_async(req),
r => r.Success,
r => r.ErrorMessage,
"remove definition");
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(() => _service.remove_algo_definition_async(req), r => r.Success, r => r.ErrorMessage, "remove template");
return success;
}
public Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) => Task.FromResult((AlgoTemplateData?)null);
public Task<bool> remove_template(AlgoKey key) =>
Task.FromResult(false);
public void Dispose() =>
Disposable.dispose(ref _dispose);
static AlgoDefinition _api_definition(AlgoData definition_data) =>
static AlgoDefinition _template(AlgoData definition_data) =>
new AlgoDefinition { Name = definition_data.name, Definition = definition_data.value.CloneToMutable() };
static AlgoDefinitionImmutable _api_definitioni(AlgoData definition_data) =>
new AlgoDefinitionImmutable(_api_definition(definition_data));
static AlgoDefinitionImmutable _templatei(AlgoData definition_data) =>
new AlgoDefinitionImmutable(_template(definition_data));
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
AlgoData.from_record(recordi);
@@ -92,5 +99,11 @@ namespace mbts.spark {
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
}
}
void _report_error(string present_progressive_desc, string msg) =>
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
}
}
@@ -7,9 +7,13 @@ using System.Threading.Tasks;
namespace mbts.spark {
interface IAlgoStudioModel {
IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key);
IAlgoDefinitionMetadataService metadata_service { get; }
Task<AlgoData?> save_definition(AlgoData definition_data);
Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data);
Task<bool> remove_definition(AlgoKey definition_key);
Task<bool> remove_template(AlgoKey template_key);
bts.config.AlgoStudioOptions ui_configuration { get; }
}
@@ -68,13 +68,7 @@ namespace mbts.spark {
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
// badge color picker palettes
custom_badge_color_palettes.AddRange(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
var root_algo_color_item = new NamedColorVM(new NamedColor { name = "(Root Algo Type)" }, false);
var algo_type_color_palette = new ColorPaletteVM(ColorPalette.factory_order_colors_palette(model.factory));
algo_type_color_palette.named_colors.Insert(0, root_algo_color_item);
custom_badge_color_palettes.Insert(0, algo_type_color_palette);
//
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
// init backing props
@@ -97,7 +91,6 @@ namespace mbts.spark {
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
_can_redo.Changed += on_can_redo_changed;
_can_undo.Changed += on_can_undo_changed;
custom_badge_color.enabled_changed += on_use_custom_badge_color_changed;
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
// load initial state
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
@@ -110,13 +103,16 @@ 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)),
_algo_root.observe_current(true).SubscribeEach(vm => {
if (vm != null) {
root_algo_color_item.assign_color(vm.meta.Color.argb_color());
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
}
return null;
}),
_algo_root.observe_current(true).SubscribeEach(vm =>
vm == null ? null :
Disposable.Create(
vm.subscribe_is_dirty().Subscribe(v => {
if (!v) {
_version.increment_version();
_on_committed_changed();
}
}),
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
Disposable.Create(() => {
_can_redo.Changed -= on_can_redo_changed;
@@ -157,10 +153,6 @@ namespace mbts.spark {
// local methods
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_use_custom_badge_color_changed() {
if (!custom_badge_color.enabled && algo_root != null)
custom_badge_color.value = algo_root.meta.Color.argb_color();
}
void on_local_changed() {
_version.increment_version();
if (algo_root != null)
@@ -198,7 +190,6 @@ namespace mbts.spark {
public AlgoDisplayGroup group => key.display_group();
public bool can_redo => _can_redo.Value;
public bool can_undo => _can_undo.Value;
//public bool use_custom_badge_color
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public Brush badge_fill => _badge_fill;
@@ -13,6 +13,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -81,19 +82,18 @@ namespace mbts.spark {
_persisted_meta = meta;
definitions_view.Refresh();
}),
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) {
val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
}
}),
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
} }),
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
// local methods
string get_revert_tooltip() {
var def = selected_definition;
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
(def.is_dirty ? $"Revert changes to '{def.name}'" : "No changes to revert") :
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
null;
}
async Task save_async(IAlgoStudioContentVM content) {
@@ -105,7 +105,7 @@ namespace mbts.spark {
if (def != null) {
switch (def.key.mode) {
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}'" : "No changes to save";
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
}
}
return null;
@@ -5,7 +5,6 @@ using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.collections;
using mbts.persist;
using mbts.spark.filters;
@@ -331,27 +330,6 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
nameof(fill_time),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(size),
nameof(price),
nameof(fill_kind),
nameof(fill_status),
nameof(fill_key),
nameof(order_key),
nameof(security_key)
});
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
}
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
}
@@ -3,8 +3,6 @@ using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -26,7 +24,6 @@ namespace mbts.spark {
FillsVM _vm;
FillsView _view;
FillsSettings _state;
SparkMenuDisplayVM _menu_display_vm;
IReadOnlyList<SparkCommand> _commands;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
@@ -41,10 +38,7 @@ namespace mbts.spark {
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state()) {
menu_display = _menu_display_vm.safe_create()
};
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
public override double MinWidth =>
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -56,13 +50,10 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
@@ -79,8 +70,7 @@ namespace mbts.spark {
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
})),
_menu_display_vm.spark_cmd
}))
};
}
@@ -88,8 +78,5 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
}
}
@@ -21,8 +21,6 @@ namespace bts.shell.fills {
public FillsFilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
public FillsSettings() { }
public FillsSettings(uint[] orders) : this(orders, null, null) { }
@@ -1,18 +1,15 @@
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.proto;
using bts.utils;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using bts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using bts.proto;
using mbts.ui.spark;
using System.Collections.Generic;
using System.ComponentModel;
using mbts.ui.settings;
namespace mbts.spark {
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
@@ -185,97 +182,33 @@ namespace mbts.spark {
_type_hash;
}
[Flags]
enum LadderFavoriteAlgoFlags {
Sticky = 1,
AutoRevert = 2,
}
/// <summary>
/// This struct contains properties for a single algo favorite item.
/// The various <see cref="AlgoPersistedKey"/> arrays in <see cref="LadderFavoriteAlgosSettings"/>,
/// can be used to create a collection of <see cref="LadderFavoriteAlgoItem"/>s.
/// In fact, the method <see cref="LadderFavoriteAlgosSettings.to_algo_items"/> does just that.
/// </summary>
readonly struct LadderFavoriteAlgoItem : IEquatable<LadderFavoriteAlgoItem> {
public readonly AlgoKey key;
public readonly int sort_index;
public readonly LadderFavoriteAlgoFlags flags;
public LadderFavoriteAlgoItem(AlgoKey key, int sort_index, LadderFavoriteAlgoFlags flags) {
this.key = key;
this.sort_index = sort_index;
this.flags = flags;
}
public bool Equals(LadderFavoriteAlgoItem other) =>
key == other.key &&
flags == other.flags;
public override bool Equals(object obj) =>
obj is LadderFavoriteAlgoItem other && Equals(other);
public override int GetHashCode() =>
key.GetHashCode();
}
[ProtoContract]
struct LadderFavoriteAlgosSettings : IEquatable<LadderFavoriteAlgosSettings> {
[ProtoMember(1)]
readonly AlgoPersistedKey[] _favorite_algos;
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
[ProtoMember(2)]
public readonly int column_count;
[ProtoMember(3)]
readonly AlgoPersistedKey[] _stickied_algos;
[ProtoMember(4)]
readonly AlgoPersistedKey[] _auto_revert_algos;
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos, AlgoKey[] auto_revert_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new AlgoPersistedKey(k.persisted)),
ArrayHelper.safe_convert(stickied_algos, k => new AlgoPersistedKey(k.persisted)),
ArrayHelper.safe_convert(auto_revert_algos, k => new AlgoPersistedKey(k.persisted)),
column_count) { }
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
column_count,
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
LadderFavoriteAlgosSettings(AlgoPersistedKey[] favorite_algos, AlgoPersistedKey[] stickied_algos, AlgoPersistedKey[] auto_revert_algos, int column_count) {
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
_favorite_algos = favorite_algos;
_stickied_algos = stickied_algos;
_auto_revert_algos = auto_revert_algos;
this.column_count = column_count;
_stickied_algos = stickied_algos;
}
public IReadOnlyList<AlgoKey> favorite_algos => _favorite_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
public IReadOnlyList<AlgoKey> stickied_algos => _stickied_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
public IReadOnlyList<AlgoKey> auto_revert_algos => _auto_revert_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
/// <summary>
/// A more sane representation of the data contained in this proto,
/// so callers don't need to join the various key lists together to determine which algos are sticky, auto revert, etc.
/// </summary>
/// <returns>An immutable dictionary of favorite algo data.</returns>
public ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> build_map() {
var map = ImmutableDictionary.CreateBuilder<AlgoKey, LadderFavoriteAlgoItem>();
if (_favorite_algos != null) {
var stickied_algo_set = new HashSet<AlgoKey>(stickied_algos);
var auto_revert_algo_set = new HashSet<AlgoKey>(auto_revert_algos);
for (int i = 0; i < _favorite_algos.Length; i++) {
var pk = _favorite_algos[i];
var k = pk.to_algo_key();
var flags = (LadderFavoriteAlgoFlags)0;
if (stickied_algo_set.Contains(k))
flags |= LadderFavoriteAlgoFlags.Sticky;
if (auto_revert_algo_set.Contains(k))
flags |= LadderFavoriteAlgoFlags.AutoRevert;
map[k] = new LadderFavoriteAlgoItem(k, i, flags);
}
}
return map.ToImmutable();
}
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
public bool Equals(LadderFavoriteAlgosSettings other) =>
column_count == other.column_count &&
favorite_algos.SequenceEqual(other.favorite_algos) &&
stickied_algos.SequenceEqual(other.stickied_algos) &&
auto_revert_algos.SequenceEqual(other.auto_revert_algos);
stickied_algos.SequenceEqual(other.stickied_algos);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
@@ -284,8 +217,7 @@ namespace mbts.spark {
EqualityComparer.aggregate_hash(
column_count,
EqualityComparer.aggregate_hash_limited(favorite_algos),
EqualityComparer.aggregate_hash_limited(stickied_algos),
EqualityComparer.aggregate_hash_limited(auto_revert_algos));
EqualityComparer.aggregate_hash_limited(stickied_algos));
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
@@ -349,7 +281,7 @@ namespace mbts.spark {
order_entry_panel_initial_width = _default_order_entry_panel_width;
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null, null);
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
show_warn_on_cross = _default_show_warn_on_cross;
enforce_max_clip = _default_enforce_max_clip;
status_message_visible = true;
@@ -70,11 +70,7 @@ namespace bts.shell {
if (version < _version_where_stickied_algos_introduced) {
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(
favorite_algos_array,
state.settings.favorite_algos.column_count,
favorite_algos_array,
state.settings.favorite_algos.auto_revert_algos.ToArray());
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
}
return load(state);
}
@@ -268,15 +268,14 @@ namespace mbts.spark {
}
}
// on Stop/Limit order updates
// When not triggered, allow for modification to StopPrice
// else treat modification as normal (Price)
public async void try_set_price(OrderRecordImmutable order, double new_price) {
// don't change stop or stop_limits => server crash
if (order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit) return;
if (order.working_quantity() <= 0) return;
_phantom_price = new_price;
RaisePropertyChanged(nameof(price));
++_n_active_price_change_ops;
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity, null)) {
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity)) {
// wait 3 secs, then give up if price change still hasn't taken effect
await Task.Delay(3000);
}
@@ -15,7 +15,6 @@ using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -747,17 +746,15 @@ namespace mbts.spark {
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
ImmutableHashSet<AlgoPersistedKey>
_sticky_algo_keys,
_auto_revert_algo_keys;
IDisposable _algo_data_hook;
HashSet<AlgoPersistedKey> _sticky_algo_keys;
IDisposable _sticky_algos_hook;
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = Disposable.Create(() => Disposable.dispose(ref _algo_data_hook));
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -774,47 +771,32 @@ namespace mbts.spark {
fai.selector.try_select(k);
return fai;
}));
_algo_data_hook?.Dispose();
_sticky_algo_keys = ImmutableHashSet.CreateRange(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
_auto_revert_algo_keys = ImmutableHashSet.CreateRange(value.auto_revert_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
var sticky_algos = _sticky_algo_keys;
var auto_revert_algos = _auto_revert_algo_keys;
_algo_data_hook = ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null && algo.key.is_persisted) {
var pk = new AlgoPersistedKey(algo.key.persisted);
// sticky
x.is_sticky = _sticky_algo_keys.Contains(pk);
sticky_algos = sticky_algos.Remove(pk);
// auto revert
x.auto_revert = _auto_revert_algo_keys.Contains(pk);
auto_revert_algos = auto_revert_algos.Remove(pk);
if (sticky_algos.Count == 0 && auto_revert_algos.Count == 0)
Disposable.dispose(ref _algo_data_hook);
}
}));
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
Disposable.replace(ref _sticky_algos_hook,
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null &&
algo.key.is_persisted &&
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
_sticky_algo_keys.Count == 0)
//
Disposable.dispose(ref _sticky_algos_hook);
})));
}
public LadderFavoriteAlgosSettings create() {
return new LadderFavoriteAlgosSettings(_raw_items
public LadderFavoriteAlgosSettings create() =>
new LadderFavoriteAlgosSettings(_raw_items
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count,
// sticky
build_filtered_algo_array(fai => fai.is_sticky),
// auto revert
build_filtered_algo_array(fai => fai.auto_revert));
AlgoKey[] build_filtered_algo_array(Func<FavoriteAlgoItem, bool> filter) =>
_raw_items
.Where(filter)
.Where(fai => fai.is_sticky)
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray();
}
.ToArray());
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
@@ -827,9 +809,7 @@ namespace mbts.spark {
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
bool
_auto_revert,
_is_sticky;
bool _is_sticky;
protected FavoriteAlgoItem() { }
@@ -840,7 +820,6 @@ namespace mbts.spark {
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
public bool auto_revert { get => _auto_revert; set => Set(ref _auto_revert, value); }
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
@@ -11,7 +11,6 @@ using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -35,6 +34,7 @@ namespace mbts.spark {
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom, _show_status_message;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double
_position,
_day_position,
@@ -49,7 +49,6 @@ namespace mbts.spark {
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
@@ -169,9 +168,17 @@ namespace mbts.spark {
foreach (var os in _cache.Values) {
var wo = os.order;
var slot_price_target = wo.ui_price();
var slot_price_target = wo.price();
var order_type = wo.order_type();
var filled = wo.filled();
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
// if the stop order has been submitted but hasn't been triggered
// we draw the badge at the stop/trigger price, else the price price
if (filled == 0) { slot_price_target = wo.stop_price(); }
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
// consistent behavior as Stop order above.
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
}
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
@@ -232,8 +239,8 @@ namespace mbts.spark {
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
return Disposable.Create(
settings.OnDispatcher().Subscribe(s => {
_favorite_algo_map = s.favorite_algos.build_map();
order_properties_vm.on_favorite_algos(_favorite_algo_map, s.favorite_algos.column_count);
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
order_properties_vm.on_favorite_algos(s.favorite_algos);
}),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
@@ -402,9 +409,9 @@ namespace mbts.spark {
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.ui_price();
double p = order.price();
foreach (var o in lineage(order.ParentKey)) {
if (_equality.Equals(p, o.ui_price()))
if (_equality.Equals(p, o.price()))
order = o;
}
return order;
@@ -503,16 +510,10 @@ namespace mbts.spark {
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
if (fav_algo != null) {
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
}
if (d.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
fav_algo.algo_root.revert_all_params();
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
}
}
@@ -28,8 +28,7 @@ using System.Windows.Media.Animation;
namespace mbts.spark {
partial class LadderGridHost : Control {
const int _min_row_count = 501, _max_row_count = 2001;
const int _min_row_count = 501, _max_row_count = 2001, _column_min_width = 8;
int _row_count = _min_row_count;
readonly VisualHost _visualHost = new VisualHost();
@@ -109,8 +108,6 @@ namespace mbts.spark {
public event Action visible_columns_changed;
public static double ColumnMinWidth => 6;
public IEnumerable<ColumnType> visible_column_types =>
columns.Where(c => c.settings.show)
.Select(c => c.column_type);
@@ -575,7 +572,7 @@ namespace mbts.spark {
case ColumnType.Price: new_col_width = (int)price_column_width; break;
case ColumnType.Ask: new_col_width = (int)ask_column_width; break;
case ColumnType.Volume: new_col_width = (int)VolumeColumnWidth; break;
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, ColumnMinWidth); break;
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, _column_min_width); break;
}
int true_adj = new_col_width - (int)column.settings.width;
column.settings.width = (uint)new_col_width;
@@ -828,7 +828,6 @@
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
@@ -838,12 +837,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}" ToolTip="Stay Enabled" Margin="4,0">
Stay Enabled
</CheckBox>
<CheckBox Grid.Column="3" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding auto_revert}" ToolTip="Auto Revert" Margin="0,0,4,0">
Auto Revert
</CheckBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -48,7 +48,7 @@
<Setter Property="BorderThickness" Value="0,0,1,0"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="23" />
<Setter Property="MinWidth" Value="{x:Static spark:LadderGridHost.ColumnMinWidth}"/>
<Setter Property="MinWidth" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
@@ -22,7 +22,7 @@ namespace mbts.spark {
DataContext = vm;
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public MiniOrderBookRowView() : base() {
FocusManager.SetIsFocusScope(this, true);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
@@ -650,13 +650,12 @@ namespace mbts.spark {
ListSortDirection? _cached_sort_direction;
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
: base(Columns.LastMessage, filter, DataGridFilterKnownTemplates.Regex) {
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DefaultColumnWidths.LastMessage;
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
@@ -4,7 +4,6 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.ui;
using mbts.ui.controls;
using System;
@@ -12,7 +11,6 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
@@ -62,35 +60,6 @@ namespace mbts.spark {
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(OrderBookRowVM),
new string[] {
nameof(security_text),
nameof(symbol),
nameof(time_created),
nameof(last_change),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(order_type),
nameof(root_algo_display_name),
nameof(order_sub_type),
nameof(side),
nameof(quantity),
nameof(filled_quantity),
nameof(price),
nameof(avg_price),
nameof(last_price),
nameof(piq),
nameof(status),
// last message (could contains commas, so skipping)
nameof(security_key),
nameof(order_key),
nameof(parent_order_key),
});
// Core
public OrderSecurityRootPair order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order.order;
@@ -119,7 +88,7 @@ namespace mbts.spark {
public DateTime last_change => order.last_change();
public string last_change_text => _fmt_date(last_change);
public OrderStatus status => order.status();
public double price => order.ui_price();
public double price => order.price();
public double last_price => order.last_price();
public double avg_price => order.avg_price();
public string price_text => _fmt_price(price);
@@ -300,14 +269,13 @@ namespace mbts.spark {
if ((Columns.OrderType & dirty) != 0)
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
// quantity
if ((Columns.Size & dirty) != 0) {
if ((Columns.Size & dirty) != 0)
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
}
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
// price
if ((Columns.Price & dirty) != 0) {
RaisePropertyChanged(nameof(price));
@@ -446,7 +414,7 @@ namespace mbts.spark {
dirty |= Columns.LastPrice;
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
dirty |= Columns.AvgFilled;
if (oldval.order.order.ui_price() != newval.order.order.ui_price())
if (oldval.order.order.price() != newval.order.order.price())
dirty |= Columns.Price;
if (oldval.order.order.quantity() != newval.order.order.quantity())
dirty |= Columns.Size;
@@ -4,11 +4,11 @@ using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils.csv;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.extensions;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -31,7 +31,6 @@ namespace mbts.spark {
OrderBookVM _vm;
OrderBookView _view;
OrderBookWindowFactory _factory;
SparkMenuDisplayVM _menu_display_vm;
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
@@ -45,14 +44,7 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
public override OrderBookStartupData SaveState {
get {
_state = _vm?.create();
if (_state != null)
_state.menu_display = _menu_display_vm.safe_create();
return _state;
}
}
public override OrderBookStartupData SaveState => _state = _vm?.create();
public override string window_type => _window_type;
public override UIElement Content => _view;
@@ -71,9 +63,6 @@ namespace mbts.spark {
}
public void thread_init() {
_menu_display_vm = new SparkMenuDisplayVM();
if (_state != null)
_menu_display_vm.load_from(_state.menu_display);
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
@@ -195,7 +184,6 @@ namespace mbts.spark {
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
toggle_show_right_panel_command,
attach_selected_orders_command,
detach_selected_orders_command,
@@ -209,8 +197,7 @@ namespace mbts.spark {
show_grid_settings_command,
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
_menu_display_vm.spark_cmd
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders))
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
@@ -238,13 +225,11 @@ namespace mbts.spark {
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) {
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_orders = false;
}));
_menu_display_vm.safe_bind_to_view(parent_view);
}
static class CommandGroups {
public const string
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_price(fs.price, o => o.order.order.price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
@@ -65,9 +65,6 @@ namespace bts.shell.orderbook {
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
@@ -99,7 +99,7 @@ namespace mbts.spark {
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public OrderBookRowView() : base() { }
}
}
@@ -1,388 +1,375 @@
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using bts.view;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace bts.shell {
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
const string _window_type = "PNL", _window_title = "PNL";
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
}
public override Task<IShellWindow> load(PNLSaveState state) =>
Task.FromResult(_create(state.settings));
void _show(PNLSettings settings) =>
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
Action<SparkWindowView> on_parent_view_created_impl = null;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var init_settings = settings ?? PNLSettings.Default;
var menu_display_vm = new SparkMenuDisplayVM();
menu_display_vm.load_from(init_settings.menu_display);
on_parent_view_created_impl = menu_display_vm.safe_bind_to_view;
var vm = new PNLVM(_bus, _orders_model, init_settings);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
}
state = defaults;
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display,
menu_display: menu_display_vm.create());
return new PNLSaveState { use_defaults = false, settings = state };
};
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
settings_manager,
us => us.pnl,
UserSettings.FileExtensions.PNL,
s => new PNLSettingsVM(s));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.RequestClose += on_request_close;
return dialog;
void on_request_close(object sender, EventArgs e) =>
WindowUtilities.force_close(dialog);
})),
menu_display_vm.spark_cmd
};
IDisposable dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
vm.load_from(s);
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
var fallback_family = new FontFamily(s.grid.font_family);
// group
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
var group_appearance = s.group_cell_appearance.to_appearance();
// header
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
var header_appearance = s.header_cell_appearance.to_appearance();
//
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
if (col_setting_map.TryGetValue(c.id, out var cs)) {
c.load_from(cs);
}
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
c.load_group_params(
group_appearance.font_size,
gcs.font_weight_override ?? group_appearance.font_weight,
gcs.font_color_override ?? group_appearance.foreground,
group_appearance.font_family);
}
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
c.load_header_params(
header_appearance.font_size,
hcs.font_weight_override ?? header_appearance.font_weight,
hcs.font_color_override ?? header_appearance.foreground,
header_appearance.font_family);
}
}
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
case NotifyCollectionChangedAction.Remove: remove_old(); break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
remove_old();
add_new();
break;
case NotifyCollectionChangedAction.Reset:
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
break;
}
void add_new() =>
cached_columns = e.NewStartingIndex >= 0 ?
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
void remove_old() =>
cached_columns = e.OldStartingIndex >= 0 ?
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
menu_display_vm,
vm);
return (content, commands, get_save_state, dispose);
},
// the on_parent_view_created_impl delegate is initialized in the main create delegate,
// so it can't be passed directly as an argument because it would just be null,
// hence the proxy/wrapper delegate below.
on_parent_view_created: pview => on_parent_view_created_impl?.Invoke(pview),
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
min_height_offset: 60);
}
readonly struct PNLConfig {
public readonly bool
enable_animations,
show_pause_resume;
public PNLConfig(bts.config.PNLOptions options) {
enable_animations = options.enable_animations;
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
}
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace bts.shell {
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
const string _window_type = "PNL", _window_title = "PNL";
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
}
public override Task<IShellWindow> load(PNLSaveState state) =>
Task.FromResult(_create(state.settings));
void _show(PNLSettings settings) =>
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
}
state = defaults;
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display);
return new PNLSaveState { use_defaults = false, settings = state };
};
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
settings_manager,
us => us.pnl,
UserSettings.FileExtensions.PNL,
s => new PNLSettingsVM(s));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.RequestClose += on_request_close;
return dialog;
void on_request_close(object sender, EventArgs e) =>
WindowUtilities.force_close(dialog);
}))
};
IDisposable dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
vm.load_from(s);
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
var fallback_family = new FontFamily(s.grid.font_family);
// group
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
var group_appearance = s.group_cell_appearance.to_appearance();
// header
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
var header_appearance = s.header_cell_appearance.to_appearance();
//
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
if (col_setting_map.TryGetValue(c.id, out var cs)) {
c.load_from(cs);
}
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
c.load_group_params(
group_appearance.font_size,
gcs.font_weight_override ?? group_appearance.font_weight,
gcs.font_color_override ?? group_appearance.foreground,
group_appearance.font_family);
}
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
c.load_header_params(
header_appearance.font_size,
hcs.font_weight_override ?? header_appearance.font_weight,
hcs.font_color_override ?? header_appearance.foreground,
header_appearance.font_family);
}
}
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
case NotifyCollectionChangedAction.Remove: remove_old(); break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
remove_old();
add_new();
break;
case NotifyCollectionChangedAction.Reset:
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
break;
}
void add_new() =>
cached_columns = e.NewStartingIndex >= 0 ?
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
void remove_old() =>
cached_columns = e.OldStartingIndex >= 0 ?
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
vm);
return (content, commands, get_save_state, dispose);
},
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
min_height_offset: 60);
}
readonly struct PNLConfig {
public readonly bool
enable_animations,
show_pause_resume;
public PNLConfig(bts.config.PNLOptions options) {
enable_animations = options.enable_animations;
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
}
@@ -224,8 +224,6 @@ namespace bts.shell.pnl {
}
[ProtoMember(6)]
public PNLHeaderRowDisplay header_row_display { get; set; }
[ProtoMember(15)]
public SparkMenuDisplaySettings menu_display { get; set; }
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
@@ -277,8 +275,7 @@ namespace bts.shell.pnl {
IEnumerable<PNLAnimationTriggerData> animations = null,
IEnumerable<PNLAnimationTriggerData> group_header_animations = null,
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
IEnumerable<GridColumnSettings> header_columns = null,
SparkMenuDisplaySettings? menu_display = null) =>
IEnumerable<GridColumnSettings> header_columns = null) =>
new PNLSettings {
calculation_type = calculation_type ?? this.calculation_type,
header_row_display = header_row_display ?? this.header_row_display,
@@ -298,8 +295,7 @@ namespace bts.shell.pnl {
_group_header_animations = group_header_animations?.ToArray() ?? _group_header_animations,
_header_row_animations = header_row_animations?.ToArray() ?? _header_row_animations,
// grid
grid = grid ?? this.grid,
menu_display = menu_display ?? this.menu_display
grid = grid ?? this.grid
};
public bool Equals(PNLSettings other) =>
@@ -490,4 +486,4 @@ namespace bts.shell.pnl {
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
}
}
}
@@ -29,10 +29,6 @@
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
<!--Converters-->
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:ValueConverterPipe x:Key="GroupRowHeightConverter">
<cv:RangeBlockConverter Minimum="{x:Static pnl:PNLView.MinGroupRowHeight}"/>
<cv:GridLengthConverter UnitType="Pixel"/>
</cv:ValueConverterPipe>
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
<cv:PartialConverter/>
<cv:ColorToBrushConverter/>
@@ -262,7 +258,7 @@
</Setter>
</Style>
<Style x:Key="TextCellBase" TargetType="c:TextCell">
<Style x:Key="RowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
@@ -273,14 +269,10 @@
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<Style x:Key="RowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -292,7 +284,20 @@
</Setter>
</Style>
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -304,8 +309,21 @@
</Setter>
</Style>
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -317,22 +335,7 @@
</Setter>
</Style>
<Style x:Key="HyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Style x:Key="HyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
@@ -346,23 +349,41 @@
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Cursor">
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<Binding Path="IsMouseOverClickArea" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Cursors.Arrow}" ValueForTrue="{x:Static Cursors.Hand}"/>
</Binding.Converter>
</Binding>
<x:Array Type="{x:Type c:CachedColumnParam}">
<c:CachedColumnParam SourcePropertyName="font_family" TargetProperty="c:HyperlinkCell.FontFamily"/>
<c:CachedColumnParam SourcePropertyName="font_size" TargetProperty="c:HyperlinkCell.FontSize"/>
<c:CachedColumnParam SourcePropertyName="font_brush" TargetProperty="c:HyperlinkCell.Foreground"/>
<c:CachedColumnParam SourcePropertyName="horizontal_alignment" TargetProperty="c:HyperlinkCell.HorizontalContentAlignment"/>
</x:Array>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Text" Value="{Binding text}"/>
</Style>
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -382,9 +403,24 @@
</Setter>
</Style>
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -418,7 +454,23 @@
</Setter>
</Style>
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -454,7 +506,7 @@
<Grid
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
c:DIYGrid.SyncWithColumnGeometry="True"
Height="{Binding RowHeight, ElementName=_grid, Converter={x:Static pnl:PNLView.HeaderRowHeightConverter}}"
Height="{Binding RowHeight, ElementName=_grid}"
>
<Grid.Visibility>
<Binding Path="header_row_display">
@@ -557,6 +609,7 @@
c:DIYGrid.CachedColumnParamsContainer="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"
VirtualizingPanel.ScrollUnit="Pixel"
ScrollViewer.CanContentScroll="True"
RowHeight="24"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding selection_highlight}"
SupportGroupItems="True"
@@ -1000,7 +1053,7 @@
<!--Row Template-->
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}">
<Grid.Background>
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
</Grid.Background>
@@ -1094,7 +1147,7 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GroupRowHeightConverter}}"/>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
@@ -1164,4 +1217,4 @@
<Rectangle Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="Gray"/>
</Grid>
</AdornerDecorator>
</UserControl>
</UserControl>
@@ -6,7 +6,6 @@ using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -32,10 +31,6 @@ namespace bts.shell.pnl {
}
partial class PNLView : UserControl {
public static readonly double
MinGroupRowHeight = 24.0.RoundLayoutValue(),
MinHeaderRowHeight = 24.0.RoundLayoutValue();
public enum Columns {
Account = 1,
// Security
@@ -90,7 +85,6 @@ namespace bts.shell.pnl {
public static readonly IValueConverter
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
public static readonly IMultiValueConverter
@@ -169,22 +163,17 @@ namespace bts.shell.pnl {
void _grid_loaded(object sender, RoutedEventArgs e) {
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
rp.SetBinding(MarginProperty,
BindingHelper.create_multi(
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += row_height;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += row_height;
return new Thickness(0, top, 0, 0);
}),
new Binding(nameof(PNLVM.header_row_display)),
new Binding {
Path = new PropertyPath(DIYGrid.RowHeightProperty),
Converter = HeaderRowHeightConverter,
Source = _grid
}));
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += 24;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += 24;
return new Thickness(0, top, 0, 0);
}),
Source = DataContext
});
}
}
#endregion
+1 -3
View File
@@ -54,8 +54,7 @@ namespace mbts.spark {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
if (close_on_send)
RequestClose?.Invoke(this, new EventArgs());
RequestClose?.Invoke(this, new EventArgs());
}
});
set_price_command = new DelegateCommand<double>(set_price);
@@ -85,7 +84,6 @@ namespace mbts.spark {
public CalculatorLogic size_calculator { get; }
public OrderPropertiesEditorVM properties_vm { get; }
public BBOVM bbo { get; }
public bool close_on_send { get; set; }
public ICommand send_order { get; }
public ICommand set_price_command { get; }
+128 -151
View File
@@ -1,113 +1,90 @@
using bts;
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class OrderParamsAggregator {
readonly TimeSpan _throttle;
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
ask => ask,
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
readonly DynamicOrderHelper _dyn_helper;
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
_dyn_helper = dyn_helper;
_throttle = throttle;
_lookup_order = lookup_order;
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
public SecurityImmutable? security => _security_mon.common_value;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
/// <summary>
/// Notifies observers when the set of <see cref="CustomOrderDefinitionImmutable"/>s changes.
/// The algos in the set are the distinct group of algos for the selected <see cref="OrderSecurityAlgo"/>s.
/// </summary>
// Note: this might be better optimized by adding a 'begin_reset' method to CommonValueMonitor that records the current state,
// and accumulates subsequent changes into a batch change. If the 'begin_reset' method returned an IDisposable,
// the batch change object could be examined when Dispose is called,
// to avoid firing the common_values_changed event if the accumulated changes cancel each other out.
// However, such a change would be non-trivial, so this method will suffice for now.
// It should perform fine in practice, even with a large number of selected orders,
// because the input is throttled.
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() =>
Observable.Create<ImmutableHashSet<CustomOrderDefinitionImmutable>>(o => {
var last_algos = ImmutableHashSet.Create(OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
return _algo_def_mon
.SubscribeThrottled(_throttle, (ask, vals) => vals, Array.Empty<CustomOrderDefinitionImmutable>())
.Subscribe(algos => {
// only notify the observer if the algo set has actually changed.
if (algos.Count != last_algos.Count || !algos.All(last_algos.Contains))
o.OnNext(last_algos = ImmutableHashSet.CreateRange(OrderDataHelper.EqualsHelpers.custom_order_defi_comparer, algos));
});
});
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable defer_updates() =>
_group_mon.defer_updates();
public void Add(OrderSecurityAlgo order_tuple) =>
_group_mon.add(order_tuple);
public void Remove(OrderSecurityAlgo order_tuple) =>
_group_mon.remove(order_tuple);
public void Clear() =>
_group_mon.clear();
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
root.order.Def.AttachTargetOrderKey == 0 ?
root.order.Def.CustomDef :
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
var lin = _lineage(root_id, attached_target);
if (lin != null && lin.Count > 0) {
var mparent = mroot;
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
using bts;
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class OrderParamsAggregator {
readonly TimeSpan _throttle;
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
ask => ask,
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
readonly DynamicOrderHelper _dyn_helper;
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
_dyn_helper = dyn_helper;
_throttle = throttle;
_lookup_order = lookup_order;
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
public SecurityImmutable? security => _security_mon.common_value;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals, Enumerable.Empty<CustomOrderDefinitionImmutable>());
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable defer_updates() =>
_group_mon.defer_updates();
public void Add(OrderSecurityAlgo order_tuple) =>
_group_mon.add(order_tuple);
public void Remove(OrderSecurityAlgo order_tuple) =>
_group_mon.remove(order_tuple);
public void Clear() =>
_group_mon.clear();
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
root.order.Def.AttachTargetOrderKey == 0 ?
root.order.Def.CustomDef :
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
var lin = _lineage(root_id, attached_target);
if (lin != null && lin.Count > 0) {
var mparent = mroot;
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
if (pcad.HasValue) {
do {
var mchild = lin.Pop().order.Def.CustomDef.CloneToMutable();
@@ -115,44 +92,44 @@ namespace mbts.spark {
mparent = mchild;
pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
} while (lin.Count > 0 && pcad.HasValue);
}
}
return mroot;
}
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
// check args/preconditions
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
if (target == root) return null;
var inner = _lookup_order(target);
if (!inner.HasValue)
return null;
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
// then push the original target onto the stack, and try to hop up each algo-generation
// via parent-key lookups until we reach the root.
// **If the lineage is disjointed, and we never reach the root, return null**
var ival = inner.Value;
var lin_stack = ival.order.attached_target_order_key() != 0 ?
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
new Stack<OrderSecurityAlgo>();
lin_stack.Push(ival);
target = ival.order.ParentKey;
while (target != root) {
if (target == 0) {
// **disjointed lineage**
return null;
}
inner = _lookup_order(target);
if (!inner.HasValue) {
// **disjointed lineage**
return null;
}
ival = inner.Value;
lin_stack.Push(ival);
target = ival.order.ParentKey;
}
return lin_stack;
}
}
}
}
}
return mroot;
}
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
// check args/preconditions
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
if (target == root) return null;
var inner = _lookup_order(target);
if (!inner.HasValue)
return null;
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
// then push the original target onto the stack, and try to hop up each algo-generation
// via parent-key lookups until we reach the root.
// **If the lineage is disjointed, and we never reach the root, return null**
var ival = inner.Value;
var lin_stack = ival.order.attached_target_order_key() != 0 ?
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
new Stack<OrderSecurityAlgo>();
lin_stack.Push(ival);
target = ival.order.ParentKey;
while (target != root) {
if (target == 0) {
// **disjointed lineage**
return null;
}
inner = _lookup_order(target);
if (!inner.HasValue) {
// **disjointed lineage**
return null;
}
ival = inner.Value;
lin_stack.Push(ival);
target = ival.order.ParentKey;
}
return lin_stack;
}
}
}
@@ -3,6 +3,7 @@ using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
@@ -13,7 +14,6 @@ using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -62,7 +62,7 @@ namespace mbts.spark {
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
IDisposable _algo_param_rows_hook;
int _favorite_algos_column_count;
bool _open_algo_popup_on_select, _use_algo_param_grid;
@@ -239,15 +239,12 @@ namespace mbts.spark {
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
Disposable.dispose(ref _algo_param_rows_hook);
if (old_value != null) {
if (old_value != null)
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
//_selected_algo_stack.Push(old_value.key);
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
if (_favorite_algo_map.TryGetValue(old_value.key, out var tup) && tup.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
old_value.algo_root.revert_all_params();
}
if (new_value != null) {
if (use_algo_param_grid)
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
@@ -343,9 +340,6 @@ namespace mbts.spark {
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
public IObservable<AlgoDefinitionVM> observe_algo_definition_vm(bool start_immediately = false) =>
_selected_algo_definition.observe_selected_item(start_immediately);
public OrderDefinition create(Side side) {
if (HasErrors)
throw new InvalidOperationException("Cannot create order when errors are present.");
@@ -371,12 +365,12 @@ namespace mbts.spark {
return order;
}
public void on_favorite_algos(ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> favorite_algo_map, int favorite_algos_column_count) {
_favorite_algo_map = favorite_algo_map;
public void on_favorite_algos(LadderFavoriteAlgosSettings value) {
_favorite_algos_index = value.favorite_algos.Select((v, i) => (v, i)).ToDictionaryCrushed(t => t.v, t => t.i);
foreach (var d in algo_definitions) {
d.favorite_index = _get_favorite_algo_index(d.key);
}
this.favorite_algos_column_count = favorite_algos_column_count;
favorite_algos_column_count = value.column_count;
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
}
@@ -389,7 +383,7 @@ namespace mbts.spark {
}
int? _get_favorite_algo_index(AlgoKey key) =>
key.mode == AlgoKey.Mode.Persisted && _favorite_algo_map.TryGetValue(key, out var tup) ? tup.sort_index : (int?)null;
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
void _on_template_metadata(IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> meta) {
_algo_metadata = meta;
@@ -129,13 +129,6 @@ namespace mbts.spark {
public static IEnumerable<IAlgoParamVM> enumerate_all_params(this IAlgoVM vm) =>
vm == null ? Enumerable.Empty<IAlgoParamVM>() : vm.algo_params.Concat(vm.child_algo_selectors.SelectMany(s => enumerate_all_params(s.selected_algo)));
public static void revert_all_params(this IAlgoVM vm) {
foreach (var p in vm.enumerate_all_params()) {
if (p.is_dirty)
p.revert();
}
}
public static IObservable<bool> subscribe_is_dirty(this IAlgoParamVM param_vm) =>
Observable.Create<bool>(o => {
o.OnNext(param_vm.is_dirty);
+2 -29
View File
@@ -35,24 +35,11 @@ namespace mbts.spark {
public static double price(this OrderRecordImmutable order) =>
order.HasState ? order.State.Price : double.NaN;
public static double ui_price(this OrderRecordImmutable order) {
if (order.HasState) {
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop && !order.stop_or_stop_limit_has_been_triggered()) {
return order.stop_price();
}
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit && !order.stop_or_stop_limit_has_been_triggered()) {
return order.stop_limit_price();
}
return order.State.Price;
}
return double.NaN;
}
public static double stop_price(this OrderRecordImmutable order) =>
order.HasState && order.State.HasStop && order.State.Stop.HasNativeState ? order.State.Stop.NativeState.StopPx : double.NaN;
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasStop ? order.Def.CustomDef.Stop.StopPrice : double.NaN;
public static double stop_limit_price(this OrderRecordImmutable order) =>
order.HasState && order.State.HasStopLimit && order.State.StopLimit.HasNativeState ? order.State.StopLimit.NativeState.StopPx : double.NaN;
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasStopLimit ? order.Def.CustomDef.StopLimit.StopPrice : double.NaN;
public static double avg_price(this OrderRecordImmutable order) =>
order.HasState ? order.State.AvgPrice : double.NaN;
@@ -261,20 +248,6 @@ namespace mbts.spark {
}
}
public static bool stop_or_stop_limit_has_been_triggered(this OrderRecordImmutable order) {
if (order.HasState && (order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit)) {
var native_state = order.State.native_order_state();
if (native_state.HasValue) {
if (order.State.HasStop || order.State.HasStopLimit) {
return native_state.Value.NativeType == NativeOrderType.Limit;
}
} else {
return false;
}
}
return false;
}
public static string type_id(this CustomOrderDefinition.CustomOneofCase order_type) =>
order_type.ToString().ToUpper();
+2 -25
View File
@@ -154,9 +154,7 @@ namespace mbts.spark {
.GroupTable(OrderSecurityHelper.key_fn, tup => tup.order.Def.SecurityKey)
.Cache();
// working account orders
var working_account_order_groups = working_orders
.Filter(o => o.order.is_exchange_order())
.GroupTable(t => t.order.Key, t => new PNLKey(t.order.account_id(), t.order.security_key()));
var working_account_order_groups = working_orders.GroupTable(t => t.order.Key, t => new PNLKey(t.order.account_id(), t.order.security_key()));
// security account pnl
security_account_pnl = on_pnl
#if DEBUG
@@ -351,21 +349,6 @@ namespace mbts.spark {
return success;
}
private CustomOrderDefinitionImmutable? _create_custom_def_for_stop_and_stop_limit(OrderRecordImmutable order, ref double stop_price) {
CustomOrderDefinition modified_custom_def = null;
var ord_type = order.order_type();
if (ord_type == CustomOrderDefinition.CustomOneofCase.Stop) {
modified_custom_def = order.Def.CustomDef.CloneToMutable();
modified_custom_def.Stop.StopPrice = stop_price;
} else if (ord_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
modified_custom_def = order.Def.CustomDef.CloneToMutable();
double temp_price = order.price();
modified_custom_def.StopLimit.StopPrice = stop_price;
stop_price = temp_price;
}
return modified_custom_def;
}
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
@@ -373,16 +356,10 @@ namespace mbts.spark {
}
var size = new_size ?? order.quantity();
var price = new_price ?? order.price();
if (!(order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit)
&& size == order.quantity() && price == order.price() && !new_algo.HasValue) {
if (size == order.quantity() && price == order.price() && !new_algo.HasValue) {
// no parameters would be changed by this update
return false;
}
if ((order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit) &&
!order.stop_or_stop_limit_has_been_triggered() && !new_algo.HasValue) {
// note the ref price parameter, if this is a stop limit then we swap the fields
new_algo = _create_custom_def_for_stop_and_stop_limit(order, ref price);
}
_activity.update_order(order, price, size, new_algo.HasValue);
var result = await _service.update_order_async(new Update() {
OrderKey = order.Key,
+3 -10
View File
@@ -525,11 +525,8 @@
<Setter.Value>
<DataTemplate>
<control:SparkMenuItem
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}}"
ContentFlowDirection="{Binding Orientation, RelativeSource={RelativeSource AncestorType=control:SparkMenu}, Converter={x:Static control:SparkMenu.ItemContentFlowDirectionConverter}}"
>
<control:SparkMenuItem.Content>
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
@@ -549,11 +546,7 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
>
<StackPanel
IsItemsHost="True"
Orientation="{Binding Orientation, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
/>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</ScrollViewer>
</ControlTemplate>
@@ -566,4 +559,4 @@
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>
-37
View File
@@ -1,15 +1,10 @@
using bts.api;
using mbts;
using mbts.ui;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using static mbts.spark.SparkTags;
@@ -65,7 +60,6 @@ namespace bts.spark {
}
}
}
public ObservableCollection<SparkCommand> child_items { get; } = new ObservableCollection<SparkCommand>();
public abstract void execute(IBus bus);
public virtual bool canExecute() => true;
@@ -334,8 +328,6 @@ namespace bts.spark {
public class SparkCommandVM : mbts.ui.shell.ViewModelBase, ICommand {
readonly SparkCommand _cmd;
readonly IBus _bus;
readonly ISelectable _selectable_cmd;
object _first_child_group;
public SparkCommandVM(SparkCommand cmd, IBus bus) {
_cmd = cmd;
@@ -344,20 +336,6 @@ namespace bts.spark {
cmd.group_changed += () => RaisePropertyChanged(nameof(group));
cmd.show_in_toolbar_changed += () => RaisePropertyChanged(nameof(show_in_toolbar));
cmd.show_in_context_menu_changed += () => RaisePropertyChanged(nameof(show_in_context_menu));
if (cmd.child_items != null) {
ObservableAutoWrapper.connect(cmd.child_items, child_items, item => new SparkCommandVM(item, bus));
child_items_view = new ListCollectionView(child_items);
child_items_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(group)));
if (child_items_view.Groups is INotifyCollectionChanged child_groups_ncc) {
child_groups_ncc.Subscribe((s, e) => refresh_first_child_group());
refresh_first_child_group();
void refresh_first_child_group() =>
first_child_group = (child_items_view.Groups.FirstOrDefault() as CollectionViewGroup)?.Name;
}
}
_selectable_cmd = cmd as ISelectable;
if (_selectable_cmd != null)
_selectable_cmd.is_selected_changed += _ => RaisePropertyChanged(nameof(is_selected));
}
public string title => _cmd.title;
@@ -366,22 +344,7 @@ namespace bts.spark {
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;
public bool is_selectable => _selectable_cmd != null;
public bool is_selected {
get => _selectable_cmd?.is_selected ?? false;
set {
if (_selectable_cmd != null)
_selectable_cmd.is_selected = value;
}
}
public ICommand command => this;
public ObservableCollection<SparkCommandVM> child_items { get; } = new ObservableCollection<SparkCommandVM>();
public ListCollectionView child_items_view { get; }
public object first_child_group {
get => _first_child_group;
set => Set(ref _first_child_group, value);
}
event EventHandler ICommand.CanExecuteChanged { add { _cmd.CanExecuteChanged += value; } remove { _cmd.CanExecuteChanged -= value; } }
bool ICommand.CanExecute(object parameter) => _cmd.canExecute();
@@ -57,9 +57,6 @@ namespace bts.spark {
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowPickOne, ShowLadder());
// Order Ticket
public static PartialCommand<IImmutableSet<uint>> ShowOrderTicketAutoLookup(KeyGesture key_gesture = null) =>
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketAutoLookupTag, key_gesture);
public static PartialCommand<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicket(KeyGesture key_gesture = null) =>
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketTag, key_gesture);
-1
View File
@@ -25,7 +25,6 @@ namespace mbts.spark {
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));
public static ITag<IImmutableSet<uint>> ShowOrderTicketAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>(nameof(ShowOrderTicketAutoLookupTag));
public static ITag<SecurityMD> ShowOrderTicketFromSecurityTag = new SparkTag<SecurityMD>(nameof(ShowOrderTicketFromSecurityTag));
public static ITag<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicketTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size)>(nameof(ShowOrderTicketTag));
#endregion
@@ -37,8 +37,6 @@ namespace mbts.ui
}
public class DelegateCommand : DelegateCommand<object> {
public static DelegateCommand empty => new DelegateCommand(() => { });
public DelegateCommand(Action execute) : this(_ => execute()) { }
public DelegateCommand(Action execute, Func<bool> can_execute) : this(_ => execute(), _ => can_execute()) { }
public DelegateCommand(Action<object> execute) : base(execute) { }
@@ -65,10 +65,8 @@ namespace mbts.ui {
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
observer = new AnonymousObserver<T>(p => {
param = p;
if (can_execute != get_can_execute(param)) {
can_execute = !can_execute;
if (can_execute != get_can_execute(param))
cmd.RaiseCanExecuteChanged();
}
});
return cmd;
}
@@ -1,22 +0,0 @@
using System;
using System.Diagnostics;
namespace bts.utils.diagnostics {
class OpTimer : IDisposable {
readonly Stopwatch _stopwatch = new Stopwatch();
public TimeSpan elapsed => _stopwatch.Elapsed;
public static OpTimer Hot() {
var ot = new OpTimer();
ot.Start();
return ot;
}
public void Start() =>
_stopwatch.Start();
public void Dispose() =>
_stopwatch.Stop();
}
}
@@ -1,41 +0,0 @@
using bts.api.core;
using bts.api.orderman;
using mbts;
using mbts.spark;
using System;
using System.Linq;
using System.Reactive.Linq;
namespace bts.utils.diagnostics {
static class TestDataFactory {
#if DEBUG
public static IObservable<TableUpdate<OrderSecurity, uint>> OrdersWithRandomPIQ(IObservable<TableUpdate<OrderSecurity, uint>> orders, TimeSpan throttle) =>
Observable.Create<TableUpdate<OrderSecurity, uint>>(o => {
TableUpdate<OrderSecurity, uint> last_orders = null;
var output = TableUpdate<OrderSecurity, uint>.mk_empty(p => p.order.Key);
var piq_offsets = Enumerable.Range(1, 10).Select(i => (uint)i).ToArray();
var rand = new Random();
return Disposable.Create(
orders.Subscribe(new_orders => {
last_orders = new_orders;
notify();
}),
Observable.Interval(throttle).Subscribe(_ => notify()));
void notify() {
var bd = last_orders.to_builder();
foreach (var os in last_orders.state.Values) {
if (os.order.is_active()) {
var rpiq = os.piq ?? 0;
rpiq += piq_offsets[rand.Next(piq_offsets.Length)];
bd.add_or_update(os.order.Key, new OrderSecurity(os.security, os.order, rpiq, os.algo, os.meta));
}
}
last_orders = bd.to_table();
if (last_orders.has_changes())
o.OnNext(last_orders);
}
}).Cache();
#endif
}
}
@@ -22,7 +22,7 @@ namespace mbts.spark {
_notification = NotificationSettings.Default,
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings,
_time_and_sales_grid = bts.shell.time_and_sales.TimeAndSalesSettings.DefaultGridSettings,
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
_order_book_grid = bts.shell.orderbook.OrderBookStartupData.DefaultGrid
};
@@ -72,7 +72,7 @@ namespace mbts.spark {
public uint next_ladder_profile_id => _last_ladder_profile_id + 1;
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.time_and_sales.TimeAndSalesSettings.DefaultColumns.all));
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
@@ -123,15 +123,15 @@ namespace bts.utils {
_deferred_adds = new RefCountMap<T>(value_comparer);
_deferred_removes = new RefCountMap<T>(value_comparer);
_value_groups = new Dictionary<TKey, RefCountMap<T>>(key_comparer);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IReadOnlyCollection<T>>(_value_groups, rcm => rcm.Keys);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.Keys);
}
public event Action<KeyValuePair<TKey, IReadOnlyCollection<T>>?> common_value_group_changed;
public event Action<(IReadOnlyCollection<T> removed, IReadOnlyCollection<T> added)> common_values_changed;
public event Action<KeyValuePair<TKey, IEnumerable<T>>?> common_value_group_changed;
public event Action<(IEnumerable<T> removed, IEnumerable<T> added)> common_values_changed;
public IReadOnlyDictionary<TKey, IReadOnlyCollection<T>> distinct_value_groups { get; }
public KeyValuePair<TKey, IReadOnlyCollection<T>>? common_value_group =>
distinct_value_groups.TryGetSingle(out KeyValuePair<TKey, IReadOnlyCollection<T>> kv) ? kv : (KeyValuePair<TKey, IReadOnlyCollection<T>>?)null;
public IReadOnlyDictionary<TKey, IEnumerable<T>> distinct_value_groups { get; }
public KeyValuePair<TKey, IEnumerable<T>>? common_value_group =>
distinct_value_groups.TryGetSingle(out KeyValuePair<TKey, IEnumerable<T>> kv) ? kv : (KeyValuePair<TKey, IEnumerable<T>>?)null;
public void add(T value) {
if (_is_deferred) {
@@ -141,7 +141,7 @@ namespace bts.utils {
var k = _key(value);
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
if (rcm.increment_adds_key(value) && _value_groups.Count == 1) {
common_values_changed?.Invoke((Array.Empty<T>(), new[] { value }));
common_values_changed?.Invoke((Enumerable.Empty<T>(), new[] { value }));
}
} else {
_value_groups.Add(k, rcm = new RefCountMap<T>(_value_comparer));
@@ -172,7 +172,7 @@ namespace bts.utils {
break;
default:
if (_value_groups.Count == 1)
common_values_changed?.Invoke((new[] { value }, Array.Empty<T>()));
common_values_changed?.Invoke((new[] { value }, Enumerable.Empty<T>()));
break;
}
}
@@ -190,7 +190,7 @@ namespace bts.utils {
switch (_value_groups.Count) {
case 1:
_value_groups.Clear();
common_values_changed?.Invoke((Array.Empty<T>(), Array.Empty<T>()));
common_values_changed?.Invoke((Enumerable.Empty<T>(), Enumerable.Empty<T>()));
common_value_group_changed?.Invoke(null);
break;
default:
@@ -230,8 +230,8 @@ namespace bts.utils {
common_value_group_changed?.Invoke(common_value_group);
} else if (last_fired_common_key is TKey k) {
common_values_changed?.Invoke((
CollectionHelper.wrap_mutable_as_read_only(_deferred_removes, kv => kv.Key),
CollectionHelper.wrap_mutable_as_read_only(_deferred_adds, kv => kv.Key)));
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
}
_deferred_adds = new RefCountMap<T>(_value_comparer);
_deferred_removes = new RefCountMap<T>(_value_comparer);
@@ -435,13 +435,7 @@ namespace bts.utils {
o.OnNext(monitor.try_get_common_value(out T result) ? selector(result) : default_value);
});
public static IObservable<TResult> SubscribeThrottled<T, TKey, TResult>(
this CommonValueGroupMonitor<T, TKey> monitor,
TimeSpan throttle,
Func<TKey, IReadOnlyCollection<T>, TResult> selector,
TResult default_value)
where TKey : struct =>
//
public static IObservable<TResult> SubscribeThrottled<T, TKey, TResult>(this CommonValueGroupMonitor<T, TKey> monitor, TimeSpan throttle, Func<TKey, IEnumerable<T>, TResult> selector, TResult default_value) where TKey : struct =>
Observable.Create<TResult>(o => {
bool busy = false, dirty = false;
notify();
@@ -451,8 +445,8 @@ namespace bts.utils {
monitor.common_values_changed -= on_values_changed;
monitor.common_value_group_changed -= on_value_group_changed;
});
void on_values_changed((IReadOnlyCollection<T>, IReadOnlyCollection<T>) _) => on_changed();
void on_value_group_changed(KeyValuePair<TKey, IReadOnlyCollection<T>>? _) => on_changed();
void on_values_changed((IEnumerable<T>, IEnumerable<T>) _) => on_changed();
void on_value_group_changed(KeyValuePair<TKey, IEnumerable<T>>? _) => on_changed();
async void on_changed() {
if (busy) {
dirty = true;
@@ -467,7 +461,7 @@ namespace bts.utils {
busy = false;
}
void notify() =>
o.OnNext(monitor.common_value_group is KeyValuePair<TKey, IReadOnlyCollection<T>> kv ? selector(kv.Key, kv.Value) : default_value);
o.OnNext(monitor.common_value_group is KeyValuePair<TKey, IEnumerable<T>> kv ? selector(kv.Key, kv.Value) : default_value);
});
public static IObservable<T?> SubscribeThrottledNullable<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) where T : struct =>
@@ -258,7 +258,7 @@ namespace bts.utils {
_property_monitors[pm_pos++] = (pad.prop_info.Name, initialize, refresh);
}
}
ArrayHelper.trim(ref _property_monitors, pm_pos);
ArrayHelper.trim(ref _property_monitors, _property_monitors.Length - pm_pos);
}
public IDisposable detect_changes(T target) {
+2 -2
View File
@@ -61,8 +61,8 @@
<SolidColorBrush x:Key="CheckBox.Pressed.BorderBrush" Color="{StaticResource CheckBox.Pressed.BorderColor}"/>
<!--ComboBox-->
<Color x:Key="ComboBoxBackgroundLightColor">#FFF0F0F0</Color>
<Color x:Key="ComboBoxBackgroundMediumColor">#FFE5E5E5</Color>
<Color x:Key="ComboBoxBackgroundLightColor">#FFE5E5E5</Color>
<Color x:Key="ComboBoxBackgroundMediumColor">#FFF0F0F0</Color>
<Color x:Key="ComboBoxBorderLightColor">#FFACACAC</Color>
<Color x:Key="ComboBoxBorderMediumColor">#FF7EB4EA</Color>
@@ -7,7 +7,7 @@ using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
public class DragRectAdorner : Adorner, IDisposable, DIYGrid.ISelectionHighlightListener {
public class DragRectAdorner : Adorner, IDisposable {
Brush _fill = DIYGrid.DefaultSelectionHighlight;
Pen _stroke = DIYGrid.DefaultSelectionStroke;
readonly ScrollViewerTuple _scroll_viewer_tup;
@@ -110,12 +110,6 @@ namespace mbts.ui.controls.adorners {
}
}
public void on_selection_fill(SolidColorBrush selection_fill) {
Fill = selection_fill;
Stroke = new Pen(selection_fill.Color.to_solid_brush(), 1);
InvalidateVisual();
}
class ScrollViewerTuple {
public readonly ScrollViewer scroll_viewer;
ScrollContentPresenter _presenter;
@@ -126,4 +120,4 @@ namespace mbts.ui.controls.adorners {
public ScrollContentPresenter presenter => _presenter ?? (_presenter = scroll_viewer.descendants_of_type<ScrollContentPresenter>().FirstOrDefault());
}
}
}
}
@@ -1,12 +1,13 @@
using mbts.ui.extensions;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using bts.utils;
using mbts.ui.extensions;
namespace mbts.ui.controls.adorners {
public class RowSelectionAdorner : Adorner, DIYGrid.ISelectionHighlightListener, IDisposable {
@@ -54,24 +55,16 @@ namespace mbts.ui.controls.adorners {
_diy_scp = _diy_rp.descendants_of_type<ScrollContentPresenter>().empty_if_null().FirstOrDefault();
// We actually don't need mouse up/down or key up/down when we can just respond to selection changes
_disposables.Add(_diy_rp.SubscribeSelectionChanged((s, e) => InvalidateVisual()));
_disposables.Add(((System.Windows.Controls.Primitives.Selector)_diy_rp).SubscribeSelectionChanged((s, e) => InvalidateVisual()));
// so that we update the highlight as the grid scrolls
_disposables.Add(_diy_scroll.SubscribeScrollChanged((s, e) => InvalidateVisual()));
// listen to ItemContainerGenerator changes
_diy_rp.ItemContainerGenerator.ItemsChanged += on_items_changed;
_diy_rp.ItemContainerGenerator.StatusChanged += on_status_changed;
_disposables.Add(Disposable.Create(() => {
_diy_rp.ItemContainerGenerator.ItemsChanged -= on_items_changed;
_diy_rp.ItemContainerGenerator.StatusChanged -= on_status_changed;
}));
void on_items_changed(object sender, ItemsChangedEventArgs e) => InvalidateVisual();
void on_status_changed(object sender, EventArgs e) => InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
if (AdornedElement is ListBox lb && lb.SelectedItems != null && lb.SelectedItems.Count > 0) {
foreach (var lbi in lb.SelectedItems) {
foreach(var lbi in lb.SelectedItems) {
if (_diy_rp.ItemContainerGenerator.ContainerFromItem(lbi) is FrameworkElement row && row.IsVisible) {
var top_left = lb.GetChildTopLeft(row);
var top_is_within_bounds = top_left.Y >= 0;
@@ -79,7 +72,7 @@ namespace mbts.ui.controls.adorners {
// Note that the top of the row can still technically be visible but we wont show
// the adorner rectangle b/c the bottom of the row will be obscured by the scrollbar
var bottom_is_within_bounds = lb.GetChildBounds(row).BottomRight.Y <= _diy_scroll.ActualHeight;
if (top_is_within_bounds && bottom_is_within_bounds) {
if (top_is_within_bounds && bottom_is_within_bounds) {
// we use the scroll content presenter in the X point calculation instead of the row
// because of the OB which has a right side panel that takes up some space over
// row width
@@ -1,83 +0,0 @@
using bts.utils;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
class SimpleOverlayAdorner : Adorner {
static readonly IComparer<(int, UIElement, Visual)> _id_comparer =
Comparer<int>.Default.wrap<int, (int, UIElement, Visual)>(tup => tup.Item1);
readonly List<(int id, UIElement target, Visual content)> _cache =
new List<(int id, UIElement target, Visual content)>();
int _child_id_counter;
public SimpleOverlayAdorner(UIElement adorned_element)
: base(adorned_element) { }
protected override int VisualChildrenCount => _cache.Count;
public IDisposable OverlayVisual(UIElement target, Visual visual) {
int id = _child_id_counter++;
_cache.Add((id, target, visual));
AddVisualChild(visual);
if (_cache.Count == 1)
this.attach();
// don't the caller prevent children from being garbage collected (returned idisposable's lifetime may exceed that of this adorner).
var weak_cache_ref = new WeakReference<List<(int, UIElement, Visual)>>(_cache);
return Disposable.Create(() => {
if (weak_cache_ref.TryGetTarget(out var loc_cache)) {
int pos = loc_cache.BinarySearch((id, null, null), _id_comparer);
if (pos >= 0) {
RemoveVisualChild(loc_cache[pos].Item3);
loc_cache.RemoveAt(pos);
}
if (loc_cache.Count == 0)
this.detach();
}
});
}
protected override Size ArrangeOverride(Size final_size) {
double xmin = final_size.Width, xmax = 0, ymin = final_size.Height, ymax = 0;
var final_rect = new Rect(new Point(0, 0), final_size);
foreach (var (id, target, content) in _cache) {
if (content is UIElement el) {
var tl = target.TranslatePoint(new Point(0, 0), AdornedElement);
var br = new Point(tl.X + target.RenderSize.Width, tl.Y + target.RenderSize.Height);
var rect = new Rect(tl, br);
rect.Intersect(final_rect);
if (!rect.Size.IsEmpty) {
el.Arrange(rect);
if (rect.Right > xmax)
xmax = rect.Right;
if (rect.Bottom > ymax)
ymax = rect.Bottom;
if (rect.Left < xmin)
xmin = rect.Left;
if (rect.Top < ymin)
ymin = rect.Top;
}
}
}
if (xmin < xmax && ymin < ymax)
return new Size(xmax - xmin, ymax - ymin);
return new Size(0, 0);
}
protected override Visual GetVisualChild(int index) =>
index >= 0 && index < _cache.Count ? _cache[index].content : null;
protected override Size MeasureOverride(Size constraint) {
foreach (var (id, target, content) in _cache) {
if (content is UIElement el)
el.Measure(target.DesiredSize);
}
return new Size(
Math.Min(constraint.Width, AdornedElement.DesiredSize.Width),
Math.Min(constraint.Height, AdornedElement.DesiredSize.Height));
}
}
}
@@ -1,7 +1,7 @@
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -83,39 +83,79 @@ namespace mbts.ui.controls {
}
}
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
if (content_width > width || unclipped_left < left)
return new Rect(left, 0, width - left + unclipped_left, row_height);
return null;
}
public static IDisposable draw_with_clip(DrawingContext dc, double raw_content_width, double raw_content_height, Thickness padding, double row_height, ColumnGeometry position) =>
draw_with_clip(dc,
raw_content_width + padding.Left + padding.Right,
raw_content_height + padding.Top + padding.Bottom,
row_height, position.left, position.unclipped_left, position.width);
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
if (clip.HasValue) {
dc.PushClip(new RectangleGeometry(clip.Value));
return Disposable.Create(dc.Pop);
} else return Disposable.Create(() => { });
}
public static void draw_text(
DrawingContext dc,
GlyphContext glyph_context,
double font_size,
string text,
ColumnGeometry position,
double row_height,
Brush foreground,
Brush background,
HorizontalAlignment x_align,
VerticalAlignment y_align,
Thickness padding,
bool round_to_layout = true) {
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
//
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
if (!string.IsNullOrEmpty(text)) {
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
}
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
if (glyph_run == null && background == null)
return;
var rect = position.to_rect(row_height);
if (background != null) {
var guidelines = rect.AntiAliasGuidelines(0);
dc.PushGuidelineSet(guidelines);
dc.DrawRectangle(background, null, rect);
dc.Pop();
}
if (glyph_run != null) {
dc.PushClip(new RectangleGeometry(rect));
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
dc.DrawGlyphRun(foreground, glyph_run);
dc.Pop();
}
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
//
using (position.clip_draw(dc, row_height)) {
double adjusted_left = position.unclipped_left;
double available_width = position.width;
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
if (trigger_actions != null) {
foreach (CellAnimation ta in trigger_actions) {
switch (ta.animation_type_case) {
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
break;
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
break;
}
}
}
foreach (var (brush, glyph) in glyphs) {
dc.DrawGlyphRun(brush, glyph);
}
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
}
@@ -125,6 +165,14 @@ namespace mbts.ui.controls {
public static void change_position(FrameworkElement cell_view, double row_height, ColumnGeometry? position) {
if (position.HasValue) {
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Visible);
var clip = get_clip(cell_view.DesiredSize, row_height, 0, position.Value.unclipped_left - position.Value.left, position.Value.width);
var el_clip = cell_view.Clip as RectangleGeometry;
if (clip.HasValue) {
if (el_clip == null || !el_clip.Rect.Equals(clip.Value))
cell_view.Clip = new RectangleGeometry(clip.Value);
} else if (cell_view.Clip != null) {
cell_view.Clip = null;
}
} else {
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Collapsed);
}
@@ -140,4 +188,4 @@ namespace mbts.ui.controls {
}
}
}
}
}
@@ -1,25 +1,16 @@
using mbts.spark;
using mbts.ui.dpi;
using mbts.ui.utilities;
using mbts.ui.utilities;
using mbts.ui.utility.text;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
enum HyperlinkCellClickMode {
Text,
Anywhere
}
class HyperlinkCell : FrameworkElement, ICommandSource {
// these fields each represent properties that are accessed frequently,
// they are stored locally to reduce repetative DependencyProperty lookups
@@ -27,35 +18,10 @@ namespace mbts.ui.controls {
Brush _background, _foreground_backing, _border_brush_backing;
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
GlyphRun _glyph_run => _gcr.glyph_run;
bool
_is_mouse_over_text,
_is_mouse_over_click_area_backing,
_can_execute,
_snaps_to_device_pixels;
bool _is_mouse_over_text, _can_execute;
Pen _underline_pen_backing, _border_stroke_pen_backing;
GlyphContentRect _gcr = new GlyphContentRect {
// add 2 pixels to account for underline (1 for space, 1 for the line itself)
content_height_offset = HighDpiExtensions.pixel_unit * 2
};
IDisposable _mouse_over_click_area_op;
GlyphContentRect _gcr = new GlyphContentRect();
bool _is_mouse_over_click_area {
get => _is_mouse_over_click_area_backing;
set {
if (_is_mouse_over_click_area_backing != value) {
Disposable.dispose(ref _mouse_over_click_area_op);
_is_mouse_over_click_area_backing = value;
if (value) {
_mouse_over_click_area_op = WorkLoop.Start(
$"Evaluate {nameof(HyperlinkCell)} {nameof(IsMouseOverClickArea)}",
TimeSpan.FromMilliseconds(100),
DispatcherPriority.Input,
_on_mouse_changed,
WorkLoop.default_excessive_cycle_ratio);
}
}
}
}
Brush _foreground {
get => _foreground_backing;
set {
@@ -71,23 +37,13 @@ namespace mbts.ui.controls {
}
}
Thickness _border_thickness {
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
get => _gcr.border_thickness;
set {
_gcr.border_thickness = new Thickness(value.Left);
_border_stroke_pen_backing = null;
}
}
Pen _underline_pen {
get {
if (_underline_pen_backing == null) {
var fg = _foreground;
if (fg != null) {
_underline_pen_backing = new Pen(fg, _snaps_to_device_pixels ? HighDpiExtensions.pixel_unit : 1);
}
}
return _underline_pen_backing;
}
}
Pen _underline_pen => _underline_pen_backing ?? (_foreground == null ? null : (_underline_pen_backing = new Pen(_foreground, 1)));
Pen _border_stroke_pen =>
// border_thickness.Left is used as the uniform border thickness. This can be changed if need be.
_border_stroke_pen_backing == null && _border_brush != null && _border_thickness.Left > 0 ?
@@ -95,8 +51,6 @@ namespace mbts.ui.controls {
_border_stroke_pen_backing;
static HyperlinkCell() {
IsMouseOverClickAreaProperty = IsMouseOverClickAreaPropertyKey.DependencyProperty;
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkCell), new FrameworkPropertyMetadata(typeof(HyperlinkCell)));
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
HorizontalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
@@ -110,11 +64,6 @@ namespace mbts.ui.controls {
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.width = v,
double.NaN);
WPFHelper.override_metadata<bool, HyperlinkCell>(
SnapsToDevicePixelsProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._snaps_to_device_pixels = v,
true);
}
public HyperlinkCell() {
@@ -124,7 +73,7 @@ namespace mbts.ui.controls {
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.padding = _underline_padding(Padding);
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
@@ -137,39 +86,8 @@ namespace mbts.ui.controls {
if (Text is string str && !string.IsNullOrEmpty(str))
_gcr.text_lines = new[] { str };
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
_snaps_to_device_pixels = SnapsToDevicePixels;
}
#region IsMouseOverClickArea
static readonly DependencyPropertyKey IsMouseOverClickAreaPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsMouseOverClickArea",
typeof(bool),
typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender,
OnIsMouseOverClickAreaChanged,
CoerceIsMouseOverClickArea));
public static readonly DependencyProperty IsMouseOverClickAreaProperty;
static void OnIsMouseOverClickAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is HyperlinkCell hlc)
hlc._is_mouse_over_click_area = e.NewValue is bool b && b;
}
static object CoerceIsMouseOverClickArea(DependencyObject d, object baseValue) =>
d is HyperlinkCell hlc ?
hlc._is_mouse_over_text || (hlc.IsMouseOver && hlc.HyperlinkClickMode == HyperlinkCellClickMode.Anywhere) :
baseValue;
public bool IsMouseOverClickArea {
get => (bool)GetValue(IsMouseOverClickAreaProperty);
private set => SetValue(IsMouseOverClickAreaPropertyKey, value);
}
#endregion
#region BorderThickness
public static readonly DependencyProperty BorderThicknessProperty =
Control.BorderThicknessProperty.AddOwner(typeof(HyperlinkCell),
@@ -204,7 +122,6 @@ namespace mbts.ui.controls {
Control.HorizontalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
HorizontalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((HyperlinkCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
@@ -218,8 +135,7 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
VerticalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((HyperlinkCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
public VerticalAlignment VerticalContentAlignment {
@@ -299,8 +215,7 @@ namespace mbts.ui.controls {
nameof(Padding),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(p, v) => p._gcr.padding = v,
DIYGrid.DefaultCellPadding);
(p, v) => p._gcr.padding = _underline_padding(v), DIYGrid.DefaultCellPadding);
public Thickness Padding { get => (Thickness)GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); }
#endregion
@@ -316,18 +231,6 @@ namespace mbts.ui.controls {
public string Text { get => GetValue(TextProperty) as string; set => SetValue(TextProperty, value); }
#endregion
#region HyperlinkClickMode
public static readonly DependencyProperty HyperlinkClickModeProperty =
WPFHelper.create_dp<HyperlinkCellClickMode, HyperlinkCell>(
nameof(HyperlinkClickMode),
(o, v) => o.CoerceValue(IsMouseOverClickAreaProperty));
public HyperlinkCellClickMode HyperlinkClickMode {
get => (HyperlinkCellClickMode)GetValue(HyperlinkClickModeProperty);
set => SetValue(HyperlinkClickModeProperty, value);
}
#endregion
#region ICommandSource
#region Command
@@ -394,8 +297,8 @@ namespace mbts.ui.controls {
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
_on_mouse_changed(e);
if (_is_mouse_over_click_area) {
_check_mouse_over_text(e);
if (_is_mouse_over_text) {
// Hyperlink should take focus when left mouse button is clicked on it
// This is consistent with all ButtonBase controls and current Win32 behavior
Focus();
@@ -405,8 +308,6 @@ namespace mbts.ui.controls {
if (e.ButtonState == MouseButtonState.Pressed) {
// Capture the mouse, and make sure we got it.
CaptureMouse();
} else {
ReleaseMouseCapture();
}
e.Handled = true;
}
@@ -414,25 +315,23 @@ namespace mbts.ui.controls {
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
base.OnMouseLeftButtonUp(e);
_on_mouse_changed(e);
if (IsMouseCaptured) {
_check_mouse_over_text(e);
if (IsMouseCaptured)
ReleaseMouseCapture();
if (_can_execute)
CommandHelper.ExecuteCommandSource(this);
_on_mouse_changed(e);
e.Handled = true;
}
if (_can_execute && _is_mouse_over_text)
CommandHelper.ExecuteCommandSource(this);
e.Handled = true;
}
#endregion
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) => _on_mouse_changed();
protected override void OnPreviewMouseMove(MouseEventArgs e) {
_on_mouse_changed(e);
e.Handled = IsMouseCaptured;
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) {
if (_is_mouse_over_text != IsMouseDirectlyOver)
_toggle_mouse_over_text();
}
protected override void OnMouseEnter(MouseEventArgs e) => _on_mouse_changed(e);
protected override void OnMouseLeave(MouseEventArgs e) => _on_mouse_changed(e);
protected override void OnPreviewMouseMove(MouseEventArgs e) => _check_mouse_over_text(e);
protected override void OnMouseEnter(MouseEventArgs e) => _check_mouse_over_text(e);
protected override void OnMouseLeave(MouseEventArgs e) => _check_mouse_over_text(e);
protected override Size ArrangeOverride(Size finalSize) => finalSize;
@@ -448,32 +347,45 @@ namespace mbts.ui.controls {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
var gr = _glyph_run;
if (gr != null) {
if (bg != null) {
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
}
if (_glyph_run != null) {
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, gr);
if (IsMouseOverClickArea) {
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
if (_is_mouse_over_text) {
// draw underline
var ul_pen = _underline_pen;
if (ul_pen != null) {
var line_start = new Point(gr.BaselineOrigin.X, gr.BaselineOrigin.Y + 2 * ul_pen.Thickness);
drawingContext.DrawHorizontalLine(ul_pen, line_start, gr.AdvanceWidths.Sum(), _snaps_to_device_pixels);
}
double x = _gcr.glyph_run.BaselineOrigin.X;
double y = _gcr.glyph_run.BaselineOrigin.Y + 2;
drawingContext.DrawLine(_underline_pen, new Point(x, y), new Point(x + _gcr.glyph_run.AdvanceWidths.Sum(), y));
}
drawingContext.Pop();
}
}
void _on_mouse_changed() =>
_on_mouse_changed(Mouse.GetPosition(this));
static Thickness _underline_padding(Thickness padding) =>
new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + 2);
void _on_mouse_changed(MouseEventArgs e) =>
_on_mouse_changed(e.GetPosition(this));
void _check_mouse_over_text(MouseEventArgs e) {
var pt = e.GetPosition(this);
var orig = _gcr.content_area;
var exp = orig.Expanded(1);
if (_is_mouse_over_text != exp.Contains(pt)) {
bts.Log.assert(_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)));
_toggle_mouse_over_text();
return;
}
if (_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)))
_toggle_mouse_over_text();
}
void _on_mouse_changed(Point pt) {
_is_mouse_over_text = _gcr.content_area.Expanded(1).Contains(pt);
CoerceValue(IsMouseOverClickAreaProperty);
void _toggle_mouse_over_text() {
_is_mouse_over_text = !_is_mouse_over_text;
if (_is_mouse_over_text)
SetCurrentValue(CursorProperty, Cursors.Hand);
else
ClearValue(CursorProperty);
InvalidateVisual();
}
}
}
@@ -1,5 +1,5 @@
using mbts.ui.dpi;
using mbts.ui.utility.text;
using mbts.ui.utility.text;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -15,7 +15,6 @@ namespace mbts.ui.controls {
Brush _background, _foreground, _border_brush_backing;
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
GlyphRun _glyph_run => _gcr.glyph_run;
bool _snaps_to_device_pixels;
Pen _border_stroke_pen_backing;
GlyphContentRect _gcr = new GlyphContentRect();
@@ -27,7 +26,7 @@ namespace mbts.ui.controls {
}
}
Thickness _border_thickness {
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
get => _gcr.border_thickness;
set {
_gcr.border_thickness = new Thickness(value.Left);
_border_stroke_pen_backing = null;
@@ -53,11 +52,6 @@ namespace mbts.ui.controls {
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.width = v,
double.NaN);
WPFHelper.override_metadata<bool, TextCell>(
SnapsToDevicePixelsProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._snaps_to_device_pixels = v,
true);
}
public TextCell() {
@@ -66,7 +60,7 @@ namespace mbts.ui.controls {
_gcr.max_width = MaxWidth;
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = MaxHeight;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
@@ -80,7 +74,6 @@ namespace mbts.ui.controls {
if (Text is string str && !string.IsNullOrEmpty(str))
_gcr.text_lines = new[] { str };
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
_snaps_to_device_pixels = SnapsToDevicePixels;
}
#region BorderThickness
@@ -117,9 +110,15 @@ namespace mbts.ui.controls {
Control.HorizontalContentAlignmentProperty.AddOwner(typeof(TextCell),
new FrameworkPropertyMetadata(
HorizontalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
(d, e) => {
if (e.OldValue is HorizontalAlignment oldha) {
if (oldha == HorizontalAlignment.Right) {
}
}
((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue;
}));
public HorizontalAlignment HorizontalContentAlignment {
get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
@@ -131,8 +130,7 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(TextCell),
new FrameworkPropertyMetadata(
VerticalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((TextCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
public VerticalAlignment VerticalContentAlignment {
@@ -239,17 +237,35 @@ namespace mbts.ui.controls {
return _gcr.layout_size;
}
double bl_x = 0;
protected override void OnRender(DrawingContext drawingContext) {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
var gr = _glyph_run;
if (gr != null) {
if (bg != null) {
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
}
if (_glyph_run != null) {
var new_x = _glyph_run.BaselineOrigin.X;
if (Math.Abs(bl_x - new_x) > 5) {
if (new_x < bl_x) {
}
}
if (Text == "-3,375.000") {
}
bl_x = new_x;
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, gr);
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
drawingContext.Pop();
}
if (bg != null) {
var bgo = DIYGrid.GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
}
}
}
}
}
@@ -36,7 +36,6 @@ namespace mbts.ui.controls {
Filled = 56,
Symbol = 78,
Time = 112,
TimeOfDay = 64,
TimeInForce = 47,
User = 53,
Aggressor = 80,
@@ -224,7 +223,7 @@ namespace mbts.ui.controls {
}
// To include extra data with your column type you are encouraged to derive from DIYGridColumn
public abstract class ColumnBase : GridViewColumn, IValueFactory<GridColumnSettings>, IDIYGridColumn, ISortableDIYGridColumn, INotifyPropertyChanged {
public abstract class ColumnBase : GridViewColumn, IValueFactory<GridColumnSettings>, INotifyPropertyChanged {
//readonly ExternalProperty<FontWeight?> _font_weigh
GridColumnSettings _settings;
FontWeight _fallback_font_weight;
@@ -363,7 +362,7 @@ namespace mbts.ui.controls {
id.ToString();
}
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IDIYGridColumn<TColKey>, ISortableDIYGridColumn<TColKey>, IGridColumn {
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IGridColumn {
TColKey _key;
bool _key_initialized;
// need a default constructor to support declaration in xaml
@@ -381,7 +380,6 @@ namespace mbts.ui.controls {
on_key_initialized(value);
}
}
TColKey IDIYGridColumn<TColKey>.id => key;
protected virtual void on_key_initialized(TColKey key) { }
}
@@ -1,40 +1,31 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui.controls {
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public readonly struct ColumnGeometry {
public readonly ColumnBase column;
public readonly DIYGridSuperHeader super_column;
public readonly double
left,
// If the column was unclipped
unclipped_left,
right;
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
: this(column, super_column, left, left, right) { }
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
this.column = column;
this.super_column = super_column;
this.left = left;
this.unclipped_left = unclipped_left;
this.right = right;
}
public double width => right - left;
public struct ColumnGeometry {
public ColumnBase column;
public DIYGridSuperHeader super_column;
public double unclipped_left; /* If the column was unclipped */
public double left;
public double width { get { return column.ActualWidth; } }
public double right { get { return unclipped_left + width; } }
public bool is_partially_obscured { get { return unclipped_left < left; } }
public double visible_width => Math.Max(0, width - left + unclipped_left);
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public Rect to_rect(double row_height) =>
new Rect(left, 0, width, row_height);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public RectangleGeometry get_clip(double row_height) =>
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
public IDisposable clip_draw(DrawingContext dc, double row_height) {
dc.PushClip(get_clip(row_height));
return Disposable.Create(dc.Pop);
}
}
}
}
@@ -1,138 +0,0 @@
using bts.utils;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.ui {
class ColumnSortManager {
readonly List<ISortableDIYGridColumn> _visible_sorting = new List<ISortableDIYGridColumn>();
readonly IList<ISortableDIYGridColumn> _columns;
readonly string _primary_key;
public ColumnSortManager(IList<ISortableDIYGridColumn> columns, string primary_key) {
_columns = columns;
_primary_key = primary_key;
}
public event Action active_sorting_changed;
public IEnumerable<ISortableDIYGridColumn> ActiveSorting {
get {
bool has_primary = false;
foreach (var col in _visible_sorting) {
if (!has_primary && col.id == _primary_key) {
has_primary = true;
}
yield return col;
}
if (!has_primary) {
var pcol = _find_column(_primary_key);
if (pcol != null)
yield return pcol;
}
}
}
public IEnumerable<ISortableDIYGridColumn> VisibleSorting =>
_visible_sorting.AsEnumerable();
public IEnumerable<SortingCriteria> SortingCriteria {
get {
foreach (var col in _visible_sorting) {
var dir = col.SortDirection;
if (dir.HasValue)
yield return new SortingCriteria(col.id, dir.Value == ListSortDirection.Ascending);
}
}
set {
var snapshot = ActiveSorting.ToList();
_clear();
if (value != null) {
foreach (var sc in value) {
var col = _find_column(sc.col_name);
if (col != null) {
col.SortDirection = sc.direction();
_visible_sorting.Add(col);
}
}
}
if (!ActiveSorting.SequenceEqual(snapshot))
active_sorting_changed?.Invoke();
}
}
public void OnColumnClick(string id, bool is_shift_pressed) =>
OnColumnClick(_find_column(id), is_shift_pressed);
public void OnColumnClick(ISortableDIYGridColumn col, bool is_shift_pressed) {
if (col == null)
return;
var old_dir = col.SortDirection;
if (!is_shift_pressed) {
_clear();
col.SortDirection = _reverse(old_dir);
_visible_sorting.Add(col);
} else {
col.SortDirection = _reverse(old_dir);
if (!old_dir.HasValue)
_visible_sorting.Add(col);
}
active_sorting_changed?.Invoke();
}
static ListSortDirection _reverse(ListSortDirection? dir) =>
dir.HasValue && dir.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
void _clear() {
_visible_sorting.Clear();
foreach (var c in _columns)
c.SortDirection = null;
}
ISortableDIYGridColumn _find_column(string id) =>
_columns.FirstOrDefault(c => c.id == id);
}
static class ColumnSortManagerExtensions {
public static IComparer<TRow> BuildComparer<TRow>(
this ColumnSortManager sort_manager,
Func<string, IComparer<TRow>> get_column_comparer,
IComparer<TRow> primary_key_comparer = null) {
//
var bd = new ComparerBuilder<TRow>();
foreach (var col in sort_manager.ActiveSorting) {
var dir = col.SortDirection;
if (dir.HasValue) {
var comp = get_column_comparer(col.id);
if (comp != null)
bd.add(_reverse_if_descending(comp, dir.Value));
}
}
if (primary_key_comparer != null)
bd.add(primary_key_comparer);
return bd.to_comparer();
}
public static IComparer<TRow> BuildComparer<TColumnKey, TRow>(
this ColumnSortManager sort_manager,
Func<TColumnKey, IComparer<TRow>> get_column_comparer) {
//
var bd = new ComparerBuilder<TRow>();
foreach (var col in sort_manager.ActiveSorting.OfType<ISortableDIYGridColumn<TColumnKey>>()) {
var dir = col.SortDirection;
if (dir.HasValue) {
var comp = get_column_comparer(col.id);
if (comp != null)
bd.add(_reverse_if_descending(comp, dir.Value));
}
}
return bd.to_comparer();
}
static IComparer<T> _reverse_if_descending<T>(IComparer<T> comp, ListSortDirection direction) =>
direction == ListSortDirection.Descending ? comp.reverse() : comp;
}
}
@@ -1,8 +1,9 @@
using mbts.spark.filters;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
@@ -21,33 +22,19 @@ namespace mbts.ui.controls {
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
public HyperlinkCellClickMode HyperlinkClickMode { get; set; }
public override FrameworkElement view_factory() {
var hl = new HyperlinkCell {
HyperlinkClickMode = HyperlinkClickMode,
Padding = padding
};
var hl = new HyperlinkCell { Padding = padding };
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
hl.SetBinding(
CursorProperty,
new Binding {
Source = hl,
Path = new PropertyPath(HyperlinkCell.IsMouseOverClickAreaProperty),
Converter = new BooleanToObjectConverter {
ValueForFalse = Cursors.Arrow,
ValueForTrue = Cursors.Hand
}
});
return hl;
}
}
}
}
}
@@ -1,50 +1,44 @@
using mbts.spark.filters;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
internal class TextColumn<TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
internal class TextColumn<TColKey> : FrameworkElementColumnBase<TColKey>, IFilterableColumn {
readonly BindingBase _binding;
public TextColumn(
TKey key, IDataGridColumnFilter filter, string filter_template) : base(key) {
public TextColumn(TColKey key, BindingBase binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
this.filter = filter;
this.filter_template = filter_template;
_binding = binding;
}
public Style ElementStyle { get; set; }
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
// bindings
public BindingBase TextBinding { get; set; }
public BindingBase ToolTipBinding { get; set; }
public BindingBase background_binding { get; set; }
public BindingBase font_weight_binding { get; set; }
public BindingBase foreground_binding { get; set; }
public BindingBase tooltip_binding { get; set; }
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
public override FrameworkElement view_factory() {
var v = new TextCell {
Padding = padding
};
// bindings
if (TextBinding != null)
v.SetBinding(TextCell.TextProperty, TextBinding);
if (ToolTipBinding != null)
v.SetBinding(ToolTipProperty, ToolTipBinding);
v.SetBinding(TextCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
var v = new TextCell { Foreground = font_brush, VerticalAlignment = vertical_alignment };
v.SetBinding(TextCell.TextProperty, _binding);
v.SetBinding(TextCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
v.SetBinding(TextCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
v.SetBinding(TextCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
v.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
// background
var bg_brush = new SolidColorBrush();
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
v.Background = bg_brush;
// style
if (ElementStyle != null)
v.Style = ElementStyle;
v.SetBinding(TextCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
v.SetBinding(TextCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
try_set_binding(TextCell.BackgroundProperty, background_binding);
try_set_binding(TextCell.ForegroundProperty, foreground_binding);
try_set_binding(TextCell.FontWeightProperty, font_weight_binding);
try_set_binding(TextCell.ToolTipProperty, tooltip_binding);
return v;
//
void try_set_binding(DependencyProperty p, BindingBase b) {
if (b != null)
v.SetBinding(p, b);
}
}
}
}
}
}
@@ -1,9 +0,0 @@
namespace mbts.ui {
public interface IDIYGridColumn {
string id { get; }
}
public interface IDIYGridColumn<T> : IDIYGridColumn {
new T id { get; }
}
}
@@ -1,9 +0,0 @@
using System.ComponentModel;
namespace mbts.ui {
interface ISortableDIYGridColumn : IDIYGridColumn {
ListSortDirection? SortDirection { get; set; }
}
interface ISortableDIYGridColumn<T> : ISortableDIYGridColumn, IDIYGridColumn<T> { }
}
@@ -2,7 +2,6 @@
using bts.utils;
using mbts.spark.filters;
using mbts.ui.controls.adorners;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.settings;
using mbts.ui.utility;
@@ -231,13 +230,11 @@ namespace mbts.ui.controls {
_scroll_viewer = this.descendants_of_type<ScrollViewer>().First();
if (SelectionMode != SelectionMode.Single) {
_drag_rect_adorner = new DragRectAdorner(this, _scroll_viewer, _row_height);
_drag_rect_adorner.SetCurrentValue(DragRectAdorner.FillProperty, _grid.SelectionHighlight);
_drag_rect_adorner.SetCurrentValue(DragRectAdorner.StrokeProperty, new Pen(_grid.SelectionHighlight.CloneCurrentValue().with(1), 1).frozen());
_drag_rect_adorner.SetResourceReference(DragRectAdorner.FillProperty, DIYGrid.ActiveSelectionHighlightKey);
_drag_rect_adorner.SetResourceReference(DragRectAdorner.StrokeProperty, DIYGrid.SelectionStrokeKey);
_drag_rect_adorner.attach();
}
_rsa = new RowSelectionAdorner(this, _scroll_viewer);
_rsa.SetCurrentValue(RowSelectionAdorner.FillProperty, _grid.SelectionHighlight);
_rsa.SetCurrentValue(RowSelectionAdorner.StrokeProperty, new Pen(_grid.SelectionHighlight.CloneCurrentValue().with(1), 1).frozen());
_rsa.attach();
};
Unloaded += (s, e) => {
@@ -446,12 +443,8 @@ namespace mbts.ui.controls {
static DIYGrid() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(typeof(DIYGrid)));
UseLayoutRoundingProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(OnUseLayoutRoundingChanged));
DefaultSelectionHighlight.Freeze();
DefaultSelectionStroke.Freeze();
void OnUseLayoutRoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
d.CoerceValue(RowHeightProperty);
}
public DIYGrid() {
@@ -1077,7 +1070,7 @@ namespace mbts.ui.controls {
if (v < min_row_height)
return min_row_height;
}
return o.UseLayoutRounding ? v.RoundLayoutValue() : v;
return v;
}, DefaultRowHeight);
public event Action<double> RowHeightChanged;
@@ -1154,30 +1147,33 @@ namespace mbts.ui.controls {
double frozen_offset = 0;
int vis_frozen_col_count = _get_visible_frozen_column_count();
foreach (var col in _visible_columns.Take(vis_frozen_col_count)) {
double right = frozen_offset + col.ActualWidth;
right = right.RoundLayoutValue();
yield return new ColumnGeometry(col, null, frozen_offset, frozen_offset, right);
yield return new ColumnGeometry() {
column = col,
left = frozen_offset,
unclipped_left = frozen_offset,
};
frozen_offset += col.ActualWidth;
}
if (_frozen_column_count > 0) {
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
}
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
// Adjust by scroll amount
double offset = frozen_offset - _scroll_horizontal.Value;
offset = offset.RoundLayoutValue();
double offset = frozen_offset - (_scroll_horizontal.Value);
int count = 0;
foreach (var col in _visible_columns.Skip(vis_frozen_col_count)) {
double left = Math.Max(offset, frozen_offset);
double right = offset + col.ActualWidth;
right = right.RoundLayoutValue();
// If right < frozen_offset column is completely obscured
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
// fully visible
yield return new ColumnGeometry(col, _superheader_column_index.ElementAtOrDefault(count), left, offset, right);
yield return new ColumnGeometry() {
column = col,
super_column = _superheader_column_index.ElementAtOrDefault(count),
unclipped_left = offset,
left = left
};
}
offset = right;
offset += col.ActualWidth;
++count;
}
}
@@ -55,7 +55,6 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="SelectionMode" Value="Extended"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="GridViewColumnHeader">
@@ -26,12 +26,12 @@ namespace mbts.ui.controls {
}
}
public class HighPerformanceRow<TRowVM> : HighPerformanceRowBase where TRowVM : class, IRowVM {
public class HighPerformanceRow<TRowVM, TColKey> : HighPerformanceRowBase where TRowVM : class, IRowVM {
double _row_height;
ObservableCollection<ColumnBase> _columns;
List<ColumnGeometry> _column_layout = new List<ColumnGeometry>();
Dictionary<string, ColumnGeometry> _column_layout_map = new Dictionary<string, ColumnGeometry>();
VisualDictionary<string, FrameworkElement> _element_cells;
Dictionary<TColKey, ColumnGeometry> _column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
VisualDictionary<TColKey, FrameworkElement> _element_cells;
RecycleBin<DIYGrid> _grid_backing;
RecycleBin<ListBoxItem> _container_backing;
TRowVM _row;
@@ -39,7 +39,7 @@ namespace mbts.ui.controls {
public HighPerformanceRow() : this(DefaultRowHeight) { }
public HighPerformanceRow(double row_height) {
_row_height = row_height;
_element_cells = new VisualDictionary<string, FrameworkElement>(this);
_element_cells = new VisualDictionary<TColKey, FrameworkElement>(this);
_grid_backing = new RecycleBin<DIYGrid>(
g => { g.ColumnLayoutChanged += _on_column_layout_changed; g.RowHeightChanged += _on_row_height_changed; },
g => { g.ColumnLayoutChanged -= _on_column_layout_changed; g.RowHeightChanged -= _on_row_height_changed; });
@@ -123,7 +123,7 @@ namespace mbts.ui.controls {
protected override Size ArrangeOverride(Size finalSize) {
foreach (var kv in _column_layout_map) {
if (_element_cells.TryGetValue(kv.Key, out FrameworkElement ec))
ec.Arrange(kv.Value.to_rect(_row_height));
ec.Arrange(new Rect(new Point(kv.Value.left, 0), new Size(kv.Value.width, _row_height)));
}
return finalSize;
}
@@ -148,18 +148,16 @@ namespace mbts.ui.controls {
}
void _add_columns(IList items) {
foreach (ColumnBase col in items) {
if (col is IFrameworkElementColumn el_col) {
var cell_view = el_col.view_factory();
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.id));
_element_cells.Add(col.id, cell_view);
}
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>()) {
var cell_view = col.view_factory();
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.key));
_element_cells.Add(col.key, cell_view);
}
}
void _remove_columns(IList items) {
foreach (ColumnBase col in items)
_element_cells.Remove(col.id);
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>())
_element_cells.Remove(col.key);
}
void _on_column_layout_changed(List<ColumnGeometry> column_layout) {
@@ -173,10 +171,10 @@ namespace mbts.ui.controls {
}
void _set_column_layout_map() {
_column_layout_map = new Dictionary<string, ColumnGeometry>();
_column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
foreach (var cg in _column_layout) {
if (cg.column is IFrameworkElementColumn)
_column_layout_map.Add(cg.column.id, cg);
if (cg.column is FrameworkElementColumnBase<TColKey> elc)
_column_layout_map.Add(elc.key, cg);
}
}
#endregion
@@ -53,7 +53,11 @@ namespace mbts.ui.controls {
if (col.is_partially_obscured) {
var obscured_width = col.left - col.unclipped_left;
grid.scroll_horizontal(-obscured_width);
return new ColumnGeometry(col.column, col.super_column, col.left, col.left, col.right);
return new ColumnGeometry() {
column = col.column,
left = col.left,
unclipped_left = col.left
};
}
return col;
}
@@ -99,7 +99,7 @@ namespace mbts.spark {
width,
horizontal_alignment,
background_color_override: is_background_color_overridden ?
new NullableColorProto(background_color_override.color) : (NullableColorProto?)null);
new NullableColorProto(background_color_override.color) : default);
var animations_lookup = predicate_animations.ToFlatLookup(pa => pa.predicate.create_assigned(owner._predicate_data_factory), pa => pa.animations.Select(a => a.create()));
return (settings, animations_lookup);
}
@@ -16,7 +16,6 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using CellAnimation = mbts.ui.CellAnimation;
using FontFamily = System.Windows.Media.FontFamily;
@@ -136,9 +135,7 @@ namespace mbts.spark {
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
}
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
background: show_background_color ?
background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color() :
Colors.Transparent.rgb_color()),
background: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
//
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateData, CellAnimation> c = null) =>
@@ -31,9 +31,6 @@
<sys:String x:Key="CannotEditValuesState">CannotEditValues</sys:String>
<sys:String x:Key="CanEditStructureState">CanEditStructure</sys:String>
<sys:String x:Key="CannotEditStructureState">CannotEditStructure</sys:String>
<Style x:Key="ParamLabel" TargetType="FrameworkElement">
<Setter Property="Margin" Value="{StaticResource Margin.LP1}"/>
</Style>
<cv:EnumHasFlagsConverter x:Key="CanEditValuesConverter">
<sp:AlgoEditingPermissions>AllowEditValues</sp:AlgoEditingPermissions>
</cv:EnumHasFlagsConverter>
@@ -51,7 +48,12 @@
</cv:ValueConverterPipe.Pipe>
</cv:ValueConverterPipe>
<Style x:Key="{x:Static usp:AlgoContentTemplateSelector.DefinitionDropDownStyleKey}" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="Margin" Value="8,0,0,0"/>
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
<Style.Triggers>
<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>
</Style>
<Style TargetType="c:TickUpDownDouble" BasedOn="{StaticResource TickUpDownDoubleFlushTextImmediately}"/>
<Style TargetType="c:TickUpDownInt" BasedOn="{StaticResource TickUpDownIntFlushTextImmediately}"/>
@@ -60,8 +62,10 @@
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="order_selector_desc"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding}" FontWeight="Bold"/>
<TextBlock Text="{Binding name}" FontWeight="Bold"/>
<!--<c:HelpButton Grid.Column="1" Margin="2" ToolTip="{Binding description}"/>-->
</Grid>
</DataTemplate>
<usp:AlgoContentTemplateSelector x:Key="AlgoTemplateSelector">
@@ -186,9 +190,9 @@
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Start</TextBlock>
@@ -287,15 +291,15 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -304,13 +308,13 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<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" Style="{StaticResource ParamLabel}">Cross Thresh</TextBlock>
<TextBlock Grid.Row="2" VerticalAlignment="Center">Cross Thresh</TextBlock>
<uag:QuantityMeasureControl x:Name="PART_CrossThreshold_QuantityMeasureControl" Grid.Row="2" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
<TextBlock Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource ParamLabel}">Cross Ticks</TextBlock>
<TextBlock Grid.Row="4" VerticalAlignment="Center">Cross Ticks</TextBlock>
<c:TickUpDownUInt x:Name="PART_CrossTicks_TickUpDownUInt" Grid.Row="4" Grid.Column="2" Value="{Binding cross_ticks}"/>
<TextBlock Grid.Row="6" VerticalAlignment="Center" Style="{StaticResource ParamLabel}">Max Chase Ticks</TextBlock>
<TextBlock Grid.Row="6" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
<c:TickUpDownUInt x:Name="PART_MaxChaseTicks_TickUpDownUInt" Grid.Row="6" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
<TextBlock Grid.Row="8" VerticalAlignment="Center" Style="{StaticResource ParamLabel}">Initial Tick Offset</TextBlock>
<TextBlock Grid.Row="8" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
<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"
@@ -360,11 +364,11 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -372,17 +376,17 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<StackPanel Orientation="Horizontal">
<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 x:Name="PART_CrossThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<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 x:Name="PART_MaxChaseTicks_TickUpDownUInt" Grid.Row="2" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
<StackPanel Grid.Row="4" Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<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>
@@ -426,7 +430,7 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -434,7 +438,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<StackPanel Orientation="Horizontal">
<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>
@@ -478,9 +482,9 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -488,16 +492,16 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<StackPanel Orientation="Horizontal">
<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 x:Name="PART_TopThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<TextBlock>Ticks Off</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="Maximum distance the order can be from the top of the book or 0 for any distance"/>
</StackPanel>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" Value="{Binding ticks_off}"/>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<TextBlock>Ticks Off</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="Maximum distance the order can be from the top of the book or 0 for any distance"/>
</StackPanel>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" Value="{Binding ticks_off}" />
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
Grid.Row="4"
@@ -537,9 +541,9 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -547,7 +551,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<StackPanel Margin="{StaticResource Margin.LP3}" Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center">Hedge Ticks</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="This is the number of ticks to offset the hedge."/>
</StackPanel>
@@ -604,11 +608,11 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -616,14 +620,14 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<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 Orientation="Horizontal">
<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 x:Name="PART_OtherTicks_TickUpDownInt" Grid.Column="2" Value="{Binding other_ticks}"/>
<StackPanel Orientation="Horizontal" Grid.Row="2" Style="{StaticResource ParamLabel}">
<TextBlock VerticalAlignment="Center">Stop Side</TextBlock>
<c:HelpButton Margin="4,0,0,0" ToolTip="The Side of the 'stop' order"/>
<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 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
@@ -675,7 +679,7 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -683,9 +687,9 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<TextBlock VerticalAlignment="Center">Tick Offset</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="How many ticks off the inside market to deploy the order, this guards against crossing the market inadventently"/>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" >Tick Offset</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="How many ticks off the inside market to deploy the order, this guards against crossing the market inadventently" />
</StackPanel>
<c:TickUpDownUInt x:Name="PART_TickOffsetOnDeploy_TickUpDownUInt" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<uag:AlgoPickerNodeView
@@ -756,24 +760,24 @@
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TimeoutSecs_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</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>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox>
@@ -786,9 +790,12 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<StackPanel
Orientation="Horizontal"
Grid.Row="1"
>
<TextBlock VerticalAlignment="Center">Price Type</TextBlock>
<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."/>
<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"
@@ -810,7 +817,7 @@
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock>Payup\less</TextBlock>
@@ -841,7 +848,7 @@
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold">Trigger Exit</TextBlock>
<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."/>
<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>
@@ -852,7 +859,7 @@
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CheckBox x:Name="PART_TimeoutEnabled_CheckBox" IsChecked="{Binding is_timeout_checked}" VerticalContentAlignment="Center">
@@ -893,7 +900,7 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -901,9 +908,9 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<TextBlock VerticalAlignment="Center">Max Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="Maximum size to show on GLBX, this is the exchange provided iceberg"/>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on GLBX, this is the exchange provided iceberg" />
</StackPanel>
<c:TickUpDownNullableUInt x:Name="PART_MaxShowQuantity_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_show_quantity}"/>
</Grid>
@@ -935,7 +942,7 @@
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -943,9 +950,9 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Style="{StaticResource ParamLabel}">
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="Maximum size to show on exchange, this is the BTS provided iceberg"/>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on exchange, this is the BTS provided iceberg" />
</StackPanel>
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding show_quantity}"/>
<uag:AlgoPickerNodeView
@@ -1015,9 +1022,8 @@
<Grid x:Name="ContentRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -1025,20 +1031,20 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Style="{StaticResource ParamLabel}">
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center">Expose Quantity</TextBlock>
<c:HelpButton Margin="4,0,0,0" ToolTip="The quantity we want visible on the exchange"/>
<StackPanel Orientation="Horizontal" Grid.Row="0" >
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
<c:HelpButton Margin="4,0,0,0" ToolTip="The quantity we want visible on the exchange" />
</StackPanel>
<c:TickUpDownUInt Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding exposed}"/>
<StackPanel Orientation="Horizontal" Grid.Row="2" Style="{StaticResource ParamLabel}">
<TextBlock Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center">Max Order Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="No single order will have a quantity greater than this quantity (Clip Size)"/>
<StackPanel Orientation="Horizontal" Grid.Row="1" >
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="No single order will have a quantity greater than this quantity (Clip Size)" />
</StackPanel>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_order_qty}"/>
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_order_qty}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
Grid.Row="4"
Grid.Row="3"
Grid.ColumnSpan="3"
ItemsSource="{Binding children_vm.child.algos_view}"
SelectedItem="{Binding children_vm.child.selected_algo}"
@@ -1047,14 +1053,14 @@
OrderKindTemplate="{StaticResource OrderKindTemplate}"
/>
</Grid>
</DataTemplate>
</DataTemplate>
</usp:AlgoContentTemplateSelector.SplitterTemplate>
<usp:AlgoContentTemplateSelector.DynamicOrderTemplate>
<DataTemplate>
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -1078,7 +1084,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<CheckBox
Grid.Column="2"
HorizontalAlignment="Stretch"
@@ -1096,7 +1102,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:TickUpDownDouble
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1113,7 +1119,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:TickUpDownInt
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1130,7 +1136,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:TickUpDownUInt
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1171,7 +1177,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<TextBox
Grid.Column="2"
HorizontalAlignment="Stretch"
@@ -1189,7 +1195,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<ComboBox
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1210,22 +1216,22 @@
<TextBlock
VerticalAlignment="Center"
Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"
Style="{StaticResource ParamLabel}" />
/>
<uag:QuantityMeasureControl
Grid.Column="2"
Focusable="False"
HorizontalAlignment="Stretch"
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl"
>
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</Grid>
</Grid>
</DataTemplate>
</usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
</usp:DynamicOrderParamTemplateSelector>
@@ -1253,7 +1259,7 @@
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush2}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -1277,7 +1283,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<CheckBox
Grid.Column="2"
HorizontalAlignment="Stretch"
@@ -1295,7 +1301,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:TickUpDownDouble
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1312,7 +1318,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:TickUpDownInt
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1329,7 +1335,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:TickUpDownUInt
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1346,7 +1352,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<TextBox
Grid.Column="2"
HorizontalAlignment="Stretch"
@@ -1364,7 +1370,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<ComboBox
Grid.Column="2"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
@@ -1374,36 +1380,34 @@
</Grid>
</DataTemplate>
</usp:GenericOrderParamTemplateSelector.EnumTemplate>
<usp:GenericOrderParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}" Style="{StaticResource ParamLabel}"/>
<uag:QuantityMeasureControl
Grid.Column="2"
Focusable="False"
HorizontalAlignment="Stretch"
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl"
>
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</Grid>
</DataTemplate>
</usp:GenericOrderParamTemplateSelector.QuantityMeasureTemplate>
<usp:GenericOrderParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center"
Text="{Binding descriptor.Name, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<uag:QuantityMeasureControl
Grid.Column="2" Focusable="False" HorizontalAlignment="Stretch"
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</Grid>
</DataTemplate>
</usp:GenericOrderParamTemplateSelector.QuantityMeasureTemplate>
</usp:GenericOrderParamTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
<ItemsControl Grid.Row="2" Grid.ColumnSpan="3" Grid.IsSharedSizeScope="True" ItemsSource="{Binding child_algo_selectors_view}" IsTabStop="False">
<ItemsControl Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding child_algo_selectors_view}" IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<uag:AlgoPickerNodeView
@@ -1418,6 +1422,6 @@
</ItemsControl>
</Grid>
</DataTemplate>
</usp:AlgoContentTemplateSelector.GenericOrderTemplate>
</usp:AlgoContentTemplateSelector.GenericOrderTemplate>
</usp:AlgoContentTemplateSelector>
</ResourceDictionary>
</ResourceDictionary>
@@ -90,9 +90,6 @@ namespace mbts.ui.spark {
}
public string name { get; }
public string description { get; }
public override string ToString() =>
name;
}
}
}
@@ -16,78 +16,6 @@
<cv:EnumHasFlagsConverter x:Key="CanEditStructureConverter">
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
</cv:EnumHasFlagsConverter>
<SolidColorBrush x:Key="Path.Static.Brush" Color="#FF333333"/>
<SolidColorBrush x:Key="Ellipse.MouseOver.Brush" Color="#FF5593FF"/>
<SolidColorBrush x:Key="Ellipse.Pressed.Brush" Color="#FF3C77DD"/>
<SolidColorBrush x:Key="Ellipse.Disabled.Brush" Color="Gray"/>
<Style x:Key="ExpanderToggleButton" TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border>
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="ellipse" Width="20" Height="20" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{Binding BorderThickness, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:ValueConverters.ThicknessToDouble}}"/>
<Path
x:Name="arrow"
Data="M0,0L3.5,3.5 0,7"
HorizontalAlignment="Center"
SnapsToDevicePixels="False"
Stroke="{StaticResource Path.Static.Brush}"
StrokeThickness="2"
VerticalAlignment="Center"
/>
<ContentPresenter
x:Name="presenter"
Grid.Column="1"
ContentSource="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M0,0L3.5,3.5 7,0"/>
</Trigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding StrokeThickness, ElementName=ellipse}" Value="0"/>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="arrow" Property="Stroke" Value="{StaticResource Ellipse.MouseOver.Brush}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding StrokeThickness, ElementName=ellipse, Converter={x:Static cv:ValueConverters.DoubleGreaterThanZero}}" Value="True"/>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="ellipse" Property="Stroke" Value="{StaticResource Ellipse.MouseOver.Brush}"/>
</MultiDataTrigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="ellipse" Value="{StaticResource Ellipse.Pressed.Brush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<!--<Setter Property="Foreground" Value="Gray"/>-->
<Setter Property="Visibility" TargetName="arrow" Value="Collapsed"/>
<Setter Property="Grid.Column" TargetName="presenter" Value="0"/>
<Setter Property="Grid.ColumnSpan" TargetName="presenter" Value="2"/>
<Setter Property="Margin" TargetName="presenter" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="AlgoPickerNodeViewTemplate" TargetType="uag:AlgoPickerNodeView">
<GroupBox
x:Name="Root"
@@ -118,23 +46,22 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ToggleButton
<c:ExpanderToggleButton
x:Name="NodeExpanderToggle"
HorizontalAlignment="Left"
VerticalAlignment="Center"
BorderThickness="0"
Margin="-4,0,0,0"
Content="{TemplateBinding OrderKind}"
ContentTemplate="{TemplateBinding OrderKindTemplate}"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsTabStop="False"
IsEnabled="{TemplateBinding CanExpand}"
Padding="-2,-2,0,0"
Style="{StaticResource ExpanderToggleButton}"
/>
<ContentControl
Grid.Column="1"
VerticalAlignment="Center"
Content="{TemplateBinding OrderKind}"
ContentTemplate="{TemplateBinding OrderKindTemplate}"
IsTabStop="False"
/>
<ComboBox
Grid.Column="2"
u:FrameworkElementStyleHelper.DynamicStyleKey="{Binding DropDownStyleKey, RelativeSource={RelativeSource TemplatedParent}}"
Margin="8,0,0,0"
HorizontalAlignment="Stretch"
IsEnabled="{Binding CanEditStructure, RelativeSource={RelativeSource TemplatedParent}, FallbackValue={u:True}}"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
@@ -159,6 +86,9 @@
/>
</GroupBox>
<ControlTemplate.Triggers>
<Trigger Property="CanExpand" Value="False">
<Setter Property="Visibility" TargetName="NodeExpanderToggle" Value="Collapsed"/>
</Trigger>
<Trigger Property="IsExpanded" Value="False">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
@@ -1,10 +1,8 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters">
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter"/>
<uspcv:OrderTypeDisplayTextConverter x:Key="OrderTypeDisplayTextConverter"/>
<Style TargetType="usp:OrderTypeItem">
@@ -28,17 +26,15 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle
<c:Badge
HorizontalAlignment="Left"
VerticalAlignment="Center"
Fill="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="Black"
StrokeThickness="1"
Background="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1"
Width="15"
Height="10"
Focusable="False"
RadiusX="{x:Static c:Badge.DefaultCornerRadius}"
RadiusY="{x:Static c:Badge.DefaultCornerRadius}"
IsTabStop="False"
/>
<TextBlock
x:Name="text"
@@ -153,7 +153,7 @@
</Grid.ColumnDefinitions>
<c:TickUpDownUInt x:Name="PART_Absolute_TickUpDown" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Style="{StaticResource AbsoluteTickUpDownStyle}"/>
<c:TickUpDownDouble x:Name="PART_Ratio_TickUpDown" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Style="{StaticResource RatioTickUpDownStyle}"/>
<CheckBox Grid.Column="1" Margin="6,0,0,0" VerticalAlignment="Center" IsChecked="{Binding QuantityCase, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource RatioQuantityCaseToBooleanConverter}}">
<CheckBox Grid.Column="1" Margin="6,0,0,0" IsChecked="{Binding QuantityCase, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource RatioQuantityCaseToBooleanConverter}}">
Ratio
</CheckBox>
</Grid>
@@ -26,19 +26,11 @@ namespace mbts.ui.spark
if (_is_selected != value) {
_is_selected = value;
IsSelectedChanged?.Invoke(security, value);
_is_selected_changed?.Invoke(value);
RaisePropertyChanged();
}
}
}
public event Action<SecurityImmutable, bool> IsSelectedChanged;
event Action<bool> _is_selected_changed;
event Action<bool> ISelectable.is_selected_changed {
add => _is_selected_changed += value;
remove => _is_selected_changed -= value;
}
public override int GetHashCode() { return security.Id.GetHashCode(); }
}
}
@@ -1,6 +1,5 @@
using bts.api;
using bts.spark;
using mbts.ui.converters;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -26,33 +25,11 @@ namespace mbts.ui.controls {
ItemsSource = items_view;
}
public static IValueConverter ItemContentFlowDirectionConverter =
ValueConverter.create<Orientation, ContentFlowDirection>(o => {
switch (o) {
case Orientation.Horizontal: return ContentFlowDirection.TopToBottom;
case Orientation.Vertical: return ContentFlowDirection.LeftToRight;
default: return ContentFlowDirection.LeftToRight;
}
});
#region ShowText
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(SparkMenu), new PropertyMetadata(true));
public bool ShowText {
get { return (bool)GetValue(ShowTextProperty); }
set { SetValue(ShowTextProperty, value); }
}
#endregion
#region Orientation
public static readonly DependencyProperty OrientationProperty =
StackPanel.OrientationProperty.AddOwner(typeof(SparkMenu), new PropertyMetadata(Orientation.Vertical));
public Orientation Orientation {
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
#endregion
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(SparkMenu), new PropertyMetadata(true));
}
}
}
@@ -1,45 +1,43 @@
using bts.api;
using bts.spark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls {
enum ContentFlowDirection {
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop
}
class SparkMenuItem : Button {
static SparkMenuItem() =>
public class SparkMenuItem : Button {
static SparkMenuItem() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenuItem), new FrameworkPropertyMetadata(typeof(SparkMenuItem)));
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(ImageSource), typeof(SparkMenuItem), new PropertyMetadata(default(ImageSource)));
public ImageSource Icon {
public ImageSource Icon
{
get { return (ImageSource)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty CollapsedProperty =
DependencyProperty.Register("Collapsed", typeof(bool), typeof(SparkMenuItem), new PropertyMetadata(default(bool)));
// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(ImageSource), typeof(SparkMenuItem), new PropertyMetadata(default(ImageSource)));
public bool Collapsed {
public bool Collapsed
{
get { return (bool)GetValue(CollapsedProperty); }
set { SetValue(CollapsedProperty, value); }
}
#region ContentFlowDirection
public static readonly DependencyProperty ContentFlowDirectionProperty =
WPFHelper.create_dp<ContentFlowDirection, SparkMenuItem>(
nameof(ContentFlowDirection),
ContentFlowDirection.LeftToRight);
// Using a DependencyProperty as the backing store for Collapsed. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CollapsedProperty =
DependencyProperty.Register("Collapsed", typeof(bool), typeof(SparkMenuItem), new PropertyMetadata(default(bool)));
public ContentFlowDirection ContentFlowDirection {
get => (ContentFlowDirection)GetValue(ContentFlowDirectionProperty);
set => SetValue(ContentFlowDirectionProperty, value);
}
#endregion
}
}
@@ -5,11 +5,11 @@ using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.converters {
namespace mbts.ui.converters
{
[ValueConversion(typeof(ImageSource), typeof(Image))]
class ImageSourceIconConverter : IValueConverter {
public static ImageSourceIconConverter Instance => new ImageSourceIconConverter();
internal class ImageSourceIconConverter : IValueConverter
{
public Thickness Margin { get; set; } = new Thickness(0);
public double Width { get; set; } = double.NaN;
public double Height { get; set; } = double.NaN;
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more