Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f56e76d4 | ||
|
|
05e6c40a57 |
No files matched your search
@@ -21,5 +21,3 @@ src/client/sparkcmd/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
@@ -39,7 +39,6 @@
|
||||
|
||||
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
|
||||
<Exec Command="
|
||||
set PATH=$(spark_build_python_path);%path% &
|
||||
set PYTHONPATH=$(Externals)\python &
|
||||
$(protoc) ^
|
||||
%(Proto3.FullPath) ^
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace bts.ui {
|
||||
public AboutDaemon(IBus bus, [Import("username")]string username, IBrowserWindowManager browser) {
|
||||
_user = username;
|
||||
_browser = browser;
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
_dispose = bus.subscribe(SparkTags.About).ObserveOnDispatcher().Subscribe(_ => show_about());
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts {
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model) {
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
|
||||
.OnDispatcher()
|
||||
.ObserveOnDispatcher()
|
||||
.Subscribe(_delete_synthetic);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.spark
|
||||
@@ -22,21 +22,18 @@ namespace bts.spark
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
_dispose = bus.subscribe(SparkTags.Support).ObserveOnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
|
||||
public void report_error(string message, string title = null, MessageBoxImage? image = null) {
|
||||
// Error reports are likely to arrive the api thread (which is not an STA thread)
|
||||
Application.Current.Dispatcher.BeginInvoke(async () => {
|
||||
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
|
||||
if (result == BTSMessageBoxButtons.Support) {
|
||||
show_support_window(message, title);
|
||||
}
|
||||
});
|
||||
public async Task report_error(string message, string title = null, MessageBoxImage? image = null) {
|
||||
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
|
||||
if (result == BTSMessageBoxButtons.Support) {
|
||||
show_support_window(message, title);
|
||||
}
|
||||
}
|
||||
|
||||
public void show_support_window(string message, string title) {
|
||||
@@ -73,11 +70,11 @@ namespace bts.spark
|
||||
return (get_successful_rv(result.value), string.Empty);
|
||||
else {
|
||||
string error_msg = template.fmt(error);
|
||||
error_reporter.report_error(error_msg);
|
||||
error_reporter.report_error(error_msg).ignore();
|
||||
return (get_unsuccessful_rv(), error_msg);
|
||||
}
|
||||
} else {
|
||||
error_reporter.report_error(result.error_message);
|
||||
error_reporter.report_error(result.error_message).ignore();
|
||||
return (get_unsuccessful_rv(), result.error_message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace bts.shell {
|
||||
_model = model;
|
||||
_error_reporter = error_reporter;
|
||||
_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.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
||||
model.on_orders.ObserveOnDispatcher().Subscribe(o => _orders = o),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).ObserveOnDispatcher().Subscribe(m =>_md_map = m),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).ObserveOnDispatcher().Subscribe(_show_modify_order)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ namespace bts {
|
||||
public SecurityPickerDaemon(IBus bus) {
|
||||
_bus = bus;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowPickOne).OnDispatcher().Subscribe(show_pick_one),
|
||||
bus.subscribe(SparkTags.FilterPickOne).OnDispatcher().Subscribe(show_filtered_pick_one),
|
||||
bus.subscribe(SparkTags.ShowPickMultiple).OnDispatcher().Subscribe(show_pick_multiple),
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).OnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
bus.subscribe(SparkTags.ShowPickOne).ObserveOnDispatcher().Subscribe(show_pick_one),
|
||||
bus.subscribe(SparkTags.FilterPickOne).ObserveOnDispatcher().Subscribe(show_filtered_pick_one),
|
||||
bus.subscribe(SparkTags.ShowPickMultiple).ObserveOnDispatcher().Subscribe(show_pick_multiple),
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).ObserveOnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace mbts.ui
|
||||
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
@@ -62,7 +62,9 @@ namespace mbts.ui
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Favorites { get { return Favorites_Gold; } }
|
||||
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
|
||||
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -3,17 +3,33 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mbts="clr-namespace:mbts.ui">
|
||||
|
||||
<DrawingImage x:Key="Bookmarks">
|
||||
<DrawingImage x:Key="FavoritesGold">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="3" Brush="Black" />
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="FavoritesWhite">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.ui {
|
||||
[Export(typeof(IShellModule))]
|
||||
public class BookmarkShellModule : ILazyMenuModule {
|
||||
readonly WindowManager _wm;
|
||||
readonly IBookmarks _bookmarks;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly WorkspacesModel _workspaces;
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarkShellModule(
|
||||
WorkspacesModel workspaces,
|
||||
WindowManager wm,
|
||||
IBookmarks bm,
|
||||
ISavedWindowFactory swf) {
|
||||
_workspaces = workspaces;
|
||||
_wm = wm;
|
||||
_bookmarks = bm;
|
||||
_window_factory = swf;
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Bookmarks;
|
||||
|
||||
public string name => "My Windows";
|
||||
|
||||
public int priority => 35;
|
||||
|
||||
public IEnumerable<SparkCommand> actions {
|
||||
get {
|
||||
// The windows that are open
|
||||
var open =
|
||||
_wm.list_documents()
|
||||
.Select(td => td.ViewModel)
|
||||
.Where(vm => vm.HasCustomTitle)
|
||||
.Select(vm =>
|
||||
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
var open_set = new HashSet<string>(open.Select(c => c.title));
|
||||
|
||||
// The windows that are not open
|
||||
var closed =
|
||||
_bookmarks.bookmarks
|
||||
.Where(state => !open_set.Contains(state.title))
|
||||
.Select(state =>
|
||||
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
|
||||
() => {
|
||||
_bookmarks.remove(state);
|
||||
return _window_factory.restore(state);
|
||||
}))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
// todo: The windows that are closed
|
||||
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
|
||||
yield return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
// This module shouldn't have any windows
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
|
||||
public interface IBookmarks {
|
||||
void save_bookmark(IShellWindow window);
|
||||
void save_bookmark(WindowState window);
|
||||
void remove(WindowState window);
|
||||
void clear();
|
||||
IEnumerable<WindowState> bookmarks { get; }
|
||||
}
|
||||
|
||||
[Export(typeof(IBookmarks))]
|
||||
public class BookmarksModel : IBookmarks {
|
||||
readonly ISettingsCollector _settings_collector;
|
||||
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarksModel(ISettingsCollector settings) {
|
||||
_settings_collector = settings;
|
||||
}
|
||||
public void save_bookmark(IShellWindow window) {
|
||||
var state = _settings_collector.get_window_state(window);
|
||||
if (state != null) {
|
||||
_bookmarks[state.title] = state;
|
||||
}
|
||||
}
|
||||
public void save_bookmark(WindowState window) {
|
||||
_bookmarks[window.title] = window;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
_bookmarks.Clear();
|
||||
}
|
||||
|
||||
public void remove(WindowState window) {
|
||||
_bookmarks.Remove(window.title);
|
||||
}
|
||||
|
||||
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
|
||||
}
|
||||
}
|
||||
@@ -78,11 +78,11 @@ namespace mbts.spark {
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
_orders_model.on_security_orders(security.Id).ObserveOnDispatcher().Subscribe(_on_orders),
|
||||
_position.ObserveOnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_inside_bids.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.ObserveOnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace mbts.spark {
|
||||
}
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
state.ObserveOnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
rows_view.SetSortingCriteria(s.sorting_criteria);
|
||||
var filters = s.filters;
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace mbts.spark {
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count < 3, secs => new SecurityCriteria(secs))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace bts.shell {
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).ObserveOnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace mbts.spark {
|
||||
_vm = new SingleValuePump<TimeAndSalesVM>();
|
||||
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("TimeAndSales Callback");
|
||||
var update_securities_cmd = SparkCommand.create("Update Time and Sales", Icon, update_securities_tag);
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).ObserveOnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).ObserveOnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
@@ -56,7 +56,7 @@ namespace mbts.spark {
|
||||
SparkCommands.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)
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(update_criteria);
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(update_criteria);
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).ObserveOnDispatcher().Subscribe(show)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace bts.ui {
|
||||
|
||||
public int priority => 40;
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
|
||||
public IEnumerable<SparkCommand> actions => new []{
|
||||
SparkCommands.ShowWorkspaces
|
||||
//SparkCommands.ShowSettings
|
||||
};
|
||||
|
||||
@@ -14,35 +14,35 @@
|
||||
<ResourceDictionary Source="/view/Resources/DataTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ParameterSectionView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/TickFactorsPicker/TickFactorsPicker.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="GroupLevelIndent">18</sys:Double>
|
||||
<sys:Double x:Key="RowHeaderWidth">285</sys:Double>
|
||||
<sys:Double x:Key="RowHeight">16</sys:Double>
|
||||
<sys:Double x:Key="NegativeRowHeight">-16</sys:Double>
|
||||
<sys:Double x:Key="ItemsHostCanBeDisabledCollapsedHeight">15</sys:Double>
|
||||
<Thickness
|
||||
x:Key="GroupLevelMargin"
|
||||
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Left="{StaticResource GroupLevelIndent}"
|
||||
/>
|
||||
<Thickness
|
||||
x:Key="GroupContentAlignedWithHeaderMargin"
|
||||
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Left="{StaticResource GroupLevelIndent}"
|
||||
Top="{StaticResource NegativeRowHeight}"
|
||||
/>
|
||||
<cv:SyntheticPriceCalcDisplayTextConverter x:Key="SyntheticPriceCalcDisplayConverter"/>
|
||||
<cv:TickFactorsTextConverter x:Key="TickFactorsTextConverter"/>
|
||||
<usp:RowHeaderGroupIndentConverter
|
||||
x:Key="RowHeaderGroupIndentConverter"
|
||||
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Indent="{StaticResource GroupLevelIndent}"
|
||||
/>
|
||||
<usp:RowHeaderGroupIndentConverter
|
||||
x:Key="ReverseRowHeaderGroupIndentConverter"
|
||||
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Indent="{StaticResource GroupLevelIndent}"
|
||||
Reverse="True"
|
||||
/>
|
||||
<usp:RowGroupExpanderWidthConverter
|
||||
x:Key="RowGroupExpanderWidthConverter"
|
||||
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Indent="{StaticResource GroupLevelIndent}"
|
||||
Offset="{StaticResource RowHeight}"
|
||||
RowHeaderWidth="{StaticResource RowHeaderWidth}"
|
||||
/>
|
||||
@@ -293,7 +293,7 @@
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataGridTextColumn x:Key="{x:Static usp:ExecutionStrategyEditor.AddColumnPlaceholderKey}" x:Shared="False" IsReadOnly="True">
|
||||
<DataGridTextColumn x:Key="AddColumnPlaceholder" x:Shared="False" IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<c:PopupControl
|
||||
ShowExpanderArrow="False" PopupPlacement="Right" Header="Add Leg" Padding="6,0"
|
||||
@@ -308,7 +308,7 @@
|
||||
</c:PopupControl>
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}">
|
||||
<DataTemplate x:Key="RemovableColumnHeaderTemplate">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -321,9 +321,7 @@
|
||||
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.NonRemovableColumnHeaderTemplateKey}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<ControlTemplate x:Key="ExecutionStrategyEditorTemplate" TargetType="usp:ExecutionStrategyEditor">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
@@ -366,11 +364,20 @@
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="6,0,0,0" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
<ComboBox
|
||||
x:Name="PART_Tick_ComboBox" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
ItemsSource="{TemplateBinding AvailableTickFactors}" SelectedItem="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource TickFactorsTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="6,0,0,0">Pricing</TextBlock>
|
||||
<ComboBox
|
||||
Grid.Column="5" Margin="6,0,0,0" IsEnabled="False"
|
||||
|
||||
@@ -25,13 +25,7 @@ namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ExecutionStrategyEditor.xaml
|
||||
/// </summary>
|
||||
partial class ExecutionStrategyEditor : Control {
|
||||
public const string
|
||||
RemovableColumnHeaderTemplateKey = "RemovableColumnHeaderTemplate",
|
||||
NonRemovableColumnHeaderTemplateKey = "ReadOnlyColumnHeaderTemplate",
|
||||
AddColumnPlaceholderKey = "AddColumnPlaceholder";
|
||||
public const double GroupLevelIndent = 18;
|
||||
|
||||
partial class ExecutionStrategyEditor : Control {
|
||||
static ExecutionStrategyEditor() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExecutionStrategyEditor), new FrameworkPropertyMetadata(typeof(ExecutionStrategyEditor)));
|
||||
|
||||
@@ -50,6 +44,13 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AvailableTickFactors
|
||||
public static readonly DependencyProperty AvailableTickFactorsProperty = DependencyProperty.Register(
|
||||
nameof(AvailableTickFactors), typeof(IEnumerable), typeof(ExecutionStrategyEditor), new PropertyMetadata(new TickFactors[0]));
|
||||
|
||||
public IEnumerable AvailableTickFactors { get => GetValue(AvailableTickFactorsProperty) as IEnumerable; set => SetValue(AvailableTickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
@@ -195,7 +196,7 @@ namespace mbts.ui.spark {
|
||||
if (dg != null) {
|
||||
var legs = LegTuples;
|
||||
dg.Columns.SetItems(legs.Select(_column_factory));
|
||||
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource(AddColumnPlaceholderKey));
|
||||
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource("AddColumnPlaceholder"));
|
||||
_dg_line_adorner = AdornerHelper.attach_continuously_on_loaded(() => new ExecutionStrategyGridLineAdorner(dg), dg);
|
||||
_legs_collection_changed_sub = (legs as INotifyCollectionChanged)?.Subscribe((s, e) => dg.Columns.ReflectChange(legs, _column_factory, e));
|
||||
}
|
||||
@@ -203,7 +204,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
|
||||
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
|
||||
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
|
||||
if (IsNew) col.HeaderTemplate = (DataTemplate)FindResource("RemovableColumnHeaderTemplate");
|
||||
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
|
||||
return col;
|
||||
}
|
||||
@@ -239,18 +240,18 @@ namespace mbts.ui.spark {
|
||||
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
|
||||
var rv = new ContentControl();
|
||||
rv.SetBinding(ContentControl.ContentProperty, new Binding());
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding("data_type") { Converter = cell_template_converter });
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
class ExecutionStrategyErrorAdorner : NotifyDataErrorAdorner<ExecutionStrategyEditor, SyntheticEditorVM>, IDisposable {
|
||||
readonly ChildProperty<TextBox> _name_text_box;
|
||||
readonly ChildProperty<TickFactorsPicker> _tick_factors_picker;
|
||||
readonly ChildProperty<ComboBox> _tick_combo_box;
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
_tick_combo_box = child_property<ComboBox>("PART_Tick_ComboBox");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
@@ -262,7 +263,7 @@ namespace mbts.ui.spark {
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.name)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _name_text_box.value, null, new Pen(Brushes.Red, 1));
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.tick_factors)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _tick_factors_picker.value, null, new Pen(Brushes.Red, 1));
|
||||
this.draw_border(drawing_context, _tick_combo_box.value, null, new Pen(Brushes.Red, 1));
|
||||
string error_text = string.Join(", ", _vm.AllErrors);
|
||||
this.draw_relative_text(RelativePosition.Below, error_text, drawing_context, _name_text_box.value, BTSColors.header_error_text_brush, 0, y_offset);
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ namespace mbts.ui.spark
|
||||
void _items_presenter_SizeChanged(object sender, SizeChangedEventArgs e) { InvalidateVisual(); }
|
||||
|
||||
protected override void draw_vertical_line(DrawingContext dc, double x, DataGridExVerticalLine line) {
|
||||
x -= GroupLevelIndent;
|
||||
var start = _grid.TranslatePoint(new Point(x, _grid.super_header_height + _col_header_presenter.ActualHeight), this);
|
||||
var stop = _grid.TranslatePoint(new Point(x, _grid.ActualHeight), this);
|
||||
dc.DrawLine(line.pen.Value, start, stop);
|
||||
if (_items_presenter != null) dc.DrawLine(line.pen.Value, new Point(x - 10, 58), new Point(x - 10, _items_presenter.ActualHeight + 58));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,9 @@ namespace bts.shell {
|
||||
void IInitializable.initialize() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
_dispose = Disposable.Create(
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).OnDispatcher().Subscribe(_show_synthetic_create),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).OnDispatcher().Subscribe(_show_synthetic_strategy),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).OnDispatcher().Subscribe(_show_synthetic_clone)
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).ObserveOnDispatcher().Subscribe(_show_synthetic_create),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).ObserveOnDispatcher().Subscribe(_show_synthetic_strategy),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).ObserveOnDispatcher().Subscribe(_show_synthetic_clone)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
HorizontalAlignment="Stretch" Margin="6"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
AvailableTickFactors="{Binding available_tick_factors_view}"
|
||||
IsNew="{Binding is_new}"
|
||||
CanUserAddNewLeg="{Binding can_add_leg}"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
|
||||
@@ -32,14 +32,14 @@ namespace mbts.spark {
|
||||
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
|
||||
() => new SecurityCriteria(), () => true
|
||||
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_mod_params)),
|
||||
can_execute, select_security_id
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
can_row_change, r => r == null ? 0 : r.security_key
|
||||
), SparkCommand.create("Change", Icons.ModifySynthetic, SparkTags.ShowSyntheticStrategyTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
r => r != null, r => r?.security ?? SecurityDataHelper.Defaults.SecurityImmutable
|
||||
can_row_change, r => r == null ? SecurityDataHelper.Defaults.SecurityImmutable : r.security
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_strategy)),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
r => r != null && r.strategy.HasValue, r => r == null ? default : (r.security, r.strategy.Value)
|
||||
), SparkCommands.ShowLadder.complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
|
||||
@@ -49,19 +49,10 @@ namespace mbts.spark {
|
||||
})
|
||||
};
|
||||
|
||||
IObservable<RowModificationParams?> observe_row_mod_params(SyntheticManagementDialogVM.Row r) =>
|
||||
r == null ? Observable.Return(new RowModificationParams?()) :
|
||||
_orders_model.security_has_working_orders(r.security_key).Select(hwo => RowModificationParams.create(r.security, hwo));
|
||||
|
||||
IObservable<RowStrategy?> observe_row_strategy(SyntheticManagementDialogVM.Row r) =>
|
||||
r == null ? Observable.Return(new RowStrategy?()) :
|
||||
_orders_model.spreader.security_strategy(r.security.Id).Select(strategy => RowStrategy.create(r.security, strategy));
|
||||
|
||||
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
|
||||
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
|
||||
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
|
||||
bool can_row_change(SyntheticManagementDialogVM.Row r) =>
|
||||
r != null && r.n_working_orders == 0;
|
||||
}
|
||||
|
||||
|
||||
public void initialize(StartupData state) {
|
||||
Logger.notnull(state);
|
||||
_state = state;
|
||||
@@ -80,31 +71,5 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
struct RowModificationParams {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly bool has_working_orders;
|
||||
|
||||
RowModificationParams(SecurityImmutable security, bool has_working_orders) {
|
||||
this.security = security;
|
||||
this.has_working_orders = has_working_orders;
|
||||
}
|
||||
|
||||
public static RowModificationParams? create(SecurityImmutable security, bool has_working_orders) =>
|
||||
new RowModificationParams(security, has_working_orders);
|
||||
}
|
||||
|
||||
struct RowStrategy {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly ExecutionStrategyImmutable strategy;
|
||||
|
||||
RowStrategy(SecurityImmutable security, ExecutionStrategyImmutable strategy) {
|
||||
this.security = security;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public static RowStrategy? create(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
|
||||
new RowStrategy(security, strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace bts.shell {
|
||||
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).ObserveOnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _enabled;
|
||||
|
||||
protected CellBase(IRowModel model, LegSpreaderParamsTuple leg_tup) =>
|
||||
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).OnDispatcher());
|
||||
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).ObserveOnDispatcher());
|
||||
|
||||
public bool enabled => _enabled.Value;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
: base(model, leg_tup) {
|
||||
_model = model;
|
||||
_leg_tup = leg_tup;
|
||||
_override_value_sub = model.observe_override_value(leg_tup).OnDispatcher().Subscribe(v => {
|
||||
_override_value_sub = model.observe_override_value(leg_tup).ObserveOnDispatcher().Subscribe(v => {
|
||||
value = v;
|
||||
RaisePropertyChanged(nameof(value));
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -11,9 +10,11 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -22,6 +23,7 @@ namespace mbts.spark {
|
||||
/// The view model designed to serve as the DataContext for the SyntheticEditorView
|
||||
/// </summary>
|
||||
partial class SyntheticEditorVM : DisposableViewModelBase, IRequestViewClose, IRequestBindingSourceUpdate, IDisposable {
|
||||
readonly ObservableCollection<TickFactors> _available_tick_factors = new ObservableCollection<TickFactors>(TickUtils.all_present_ticks());
|
||||
readonly QuantityModel _quantity_model;
|
||||
readonly PriceMultiplierModel _price_mult_model;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>();
|
||||
@@ -42,10 +44,10 @@ namespace mbts.spark {
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
title = $"Edit '{synthetic.Synthetic.Name}'";
|
||||
icon = Icons.ModifySynthetic;
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.ObserveOnDispatcher());
|
||||
var commit = new ObservantAsyncCommand(commit_async);
|
||||
_resources.Add(security_has_working_orders.Select(v => !v).OnDispatcher().Subscribe(commit));
|
||||
_resources.Add(_has_working_orders.OnDispatcher().Subscribe(vals => {
|
||||
_resources.Add(security_has_working_orders.Select(v => !v).ObserveOnDispatcher().Subscribe(commit));
|
||||
_resources.Add(_has_working_orders.ObserveOnDispatcher().Subscribe(vals => {
|
||||
if (vals.newval)
|
||||
set_error(nameof(has_working_orders), "Security has working orders");
|
||||
else clear_error(nameof(has_working_orders));
|
||||
@@ -119,7 +121,7 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
if (_tick_factors == null || !_tick_factors.is_valid() || !_available_tick_factors.Contains(_tick_factors)) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
@@ -131,8 +133,6 @@ namespace mbts.spark {
|
||||
_synthetic.ContractSpec.LowTick = tick_frac;
|
||||
_synthetic.ContractSpec.HighTick = tick_frac;
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
uint? id = await model.create_synthetic(_synthetic);
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -152,6 +152,8 @@ namespace mbts.spark {
|
||||
_set_ticks_automatically = is_new;
|
||||
_set_quantities_automatically = is_new;
|
||||
_set_price_mults_automatically = is_new;
|
||||
available_tick_factors_view = CollectionViewSource.GetDefaultView(_available_tick_factors);
|
||||
available_tick_factors_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickFactors.tick_base)));
|
||||
|
||||
_quantity_model = new QuantityModel(!is_new);
|
||||
_price_mult_model = new PriceMultiplierModel(!is_new);
|
||||
@@ -171,8 +173,7 @@ namespace mbts.spark {
|
||||
}
|
||||
_is_setting_price_mults = false;
|
||||
};
|
||||
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
@@ -243,11 +244,9 @@ namespace mbts.spark {
|
||||
public TickFactors tick_factors {
|
||||
get => _tick_factors;
|
||||
set {
|
||||
if (_tick_factors != value) {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
@@ -261,6 +260,7 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
public ICollectionView available_tick_factors_view { get; }
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -327,6 +327,8 @@ namespace mbts.spark {
|
||||
|
||||
void _deduce_ticks() {
|
||||
_tick_factors = TickUtils.deduce_ticks(leg_tuples.Select(l => l.leg.Security.ContractSpec));
|
||||
if (_tick_factors != null && !_available_tick_factors.Contains(tick_factors))
|
||||
_available_tick_factors.Add(_tick_factors);
|
||||
RaisePropertyChanged(nameof(tick_factors));
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace mbts.spark {
|
||||
.Select(t => (uint)(t.state
|
||||
.Where(kv => kv.Value.is_active())
|
||||
.Count())
|
||||
).OnDispatcher());
|
||||
).ObserveOnDispatcher());
|
||||
name = security.Synthetic.Name;
|
||||
n_legs = security.Synthetic.Legs.Count();
|
||||
description = SecurityFormatter.format_contract_legs(security.Synthetic.Legs);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace bts.shell
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
_dispose = _message_src.OnDispatcher().Subscribe(_messages.AddLog);
|
||||
_dispose = _message_src.ObserveOnDispatcher().Subscribe(_messages.AddLog);
|
||||
_content =
|
||||
new LogViewer {
|
||||
ItemsSource = _messages,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
public HelpShellModule(IBus bus, IBrowserWindowManager browser) {
|
||||
_awf = new ActivityWindowFactory(this, bus);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowUri).OnDispatcher().Subscribe(u => browser.Pages.Add(u))
|
||||
bus.subscribe(SparkTags.ShowUri).ObserveOnDispatcher().Subscribe(u => browser.Pages.Add(u))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,16 +24,13 @@ namespace mbts.spark {
|
||||
public FillsVM(IBus bus, OrdersModel model, StartupData state)
|
||||
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<ITableUpdateTransform<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateTableUpdate(fills_source), sorting_criteria) {
|
||||
_model = model;
|
||||
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts(), r => r.account, nameof(Row.account));
|
||||
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users(), r => r.user, nameof(Row.user));
|
||||
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
|
||||
fills_source.with_respect_to(
|
||||
ft => ft.security_md.HasValue ? ft.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
s => s.Id, ft => ft.key
|
||||
).Select(t => (t.added.Values, t.removed.Values)),
|
||||
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added, t.removed)),
|
||||
r => r.security,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
@@ -129,8 +126,8 @@ namespace mbts.spark {
|
||||
protected override Row row_factory(FillTuple row_model) => new Row(row_model, _model);
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.fill_time), false); }
|
||||
|
||||
static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
static IObservable<ITableUpdateTransform<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
if (state.orders != null && state.orders.Length == 0) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples;
|
||||
@@ -141,7 +138,7 @@ namespace mbts.spark {
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security ?? SecurityDataHelper.Defaults.Security) {
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts.shell {
|
||||
: base(module, "Fills") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).ObserveOnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -30,11 +30,12 @@ namespace bts.shell {
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_max_ladders_open = (int)(Configuration.get<bts.config.LadderKnobs>().max_open_ladders);
|
||||
var window_props = Configuration.get<bts.config.IShellWindowProps>();
|
||||
_max_ladders_open = (int)(window_props?.ladder?.max_total_open ?? 75);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
bus.subscribe(SparkTags.ShowLadderTag).ObserveOnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).ObserveOnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +77,6 @@ namespace bts.shell {
|
||||
s.ask_break_line = LadderSettings.Default.ask_break_line;
|
||||
if (s.mid_break_line == null)
|
||||
s.mid_break_line = LadderSettings.Default.mid_break_line;
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).ObserveOnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(OrderBookStartupData state) {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).ObserveOnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
//
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source.OnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source.ObserveOnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
_price = backing(nameof(price), double.NaN);
|
||||
size_calculator = new CalculatorLogic(null, calculator_buttons);
|
||||
_current_price = backing(nameof(current_price), order_source.Select(ort => ort.order.State.Price));
|
||||
@@ -83,15 +83,15 @@ namespace mbts.spark {
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
bbo = new BBOVM(security_md_source),
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
order_source.ObserveOnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
price = ort.order.State.Price;
|
||||
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
|
||||
size_calculator.bypass_value_setter(ort.order.State.Quantity);
|
||||
}),
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).ObserveOnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().ObserveOnDispatcher().Subscribe(modify_order));
|
||||
this.modify_order = modify_order;
|
||||
set_price_command = new DelegateCommand<double>(_price.set);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,32 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
namespace mbts.spark
|
||||
{
|
||||
class FillTuple : IEquatable<FillTuple> {
|
||||
public readonly FillImmutable fill;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
public readonly SecurityMD? security_md;
|
||||
public readonly FillTupleKey key;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly string key;
|
||||
|
||||
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityMD? security_md) {
|
||||
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityImmutable? security) {
|
||||
this.fill = fill;
|
||||
this.order = order;
|
||||
this.security_md = security_md;
|
||||
key = new FillTupleKey(fill.FillKey, order.HasValue ? order.Value.Key : 0u, security_md.HasValue ? security_md.Value.security.Id : 0u);
|
||||
this.security = security;
|
||||
// a FillTuple that DOES NOT HAVE associated order/security needs to be distinguishable
|
||||
// from a FillTuple (of the same underlying FillImmutable) that DOES HAVE associated order/security
|
||||
key = fill.FillKey;
|
||||
if (!order.HasValue) key += "!o";
|
||||
if (!security.HasValue) key += "!s";
|
||||
}
|
||||
|
||||
public bool Equals(FillTuple other) => other != null
|
||||
&& other.order.HasValue == order.HasValue
|
||||
&& other.security_md.HasValue == security_md.HasValue
|
||||
&& other.security.HasValue == security.HasValue
|
||||
&& other.fill.Equals(fill);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FillTuple);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
}
|
||||
|
||||
struct FillTupleKey : IEquatable<FillTupleKey> {
|
||||
public readonly string fill_key;
|
||||
public readonly uint order_key, security_key;
|
||||
|
||||
public FillTupleKey(string fill_key, uint order_key, uint security_key) {
|
||||
this.fill_key = fill_key;
|
||||
this.order_key = order_key;
|
||||
this.security_key = security_key;
|
||||
}
|
||||
|
||||
public bool Equals(FillTupleKey other) =>
|
||||
other.order_key == order_key &&
|
||||
other.security_key == security_key &&
|
||||
other.fill_key == fill_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is FillTupleKey k && Equals(k);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
fill_key.GetHashCode(),
|
||||
(int)order_key,
|
||||
(int)security_key);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api.core;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using mbts.collections;
|
||||
using mbts.utils;
|
||||
@@ -6,25 +7,23 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillTupleSource : IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>>, IDisposable {
|
||||
// returns a ref-count Observable.Create-wrapper that initializes/destroys the [hot-observable] FillTupleSource.
|
||||
// (initializes when the first subscription is made, and destroys when the last subscription is disposed).
|
||||
public static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> create_observable(
|
||||
class FillTupleSource : IDisposable {
|
||||
public static IObservable<ITableUpdateTransform<FillTuple, string>> create_observable(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) {
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills
|
||||
) {
|
||||
FillTupleSource source = null;
|
||||
int ref_count = 0;
|
||||
return Observable.Create<IIncrementalUpdate<FillTuple, FillTupleKey>>(observer => {
|
||||
return Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
|
||||
if (Interlocked.Increment(ref ref_count) == 1) {
|
||||
SparkUtils.lazy_assert(source == null, () => fmt_assert_msg("source should be null, there must be a leak."));
|
||||
source = new FillTupleSource(securities, orders, fills);
|
||||
}
|
||||
IDisposable sub = source.Subscribe(observer);
|
||||
IDisposable sub = source.fill_tuples.Subscribe(observer);
|
||||
return Disposable.Create(() => {
|
||||
Disposable.dispose_once(ref sub);
|
||||
if (Interlocked.Decrement(ref ref_count) == 0)
|
||||
@@ -34,157 +33,153 @@ namespace mbts.spark {
|
||||
string fmt_assert_msg(string msg) => $"{nameof(FillTupleSource)}.{nameof(create_observable)}: {msg}";
|
||||
}
|
||||
|
||||
readonly Subject<IIncrementalUpdate<FillTuple, FillTupleKey>> _subject = new Subject<IIncrementalUpdate<FillTuple, FillTupleKey>>();
|
||||
readonly Dictionary<FillTupleKey, FillTuple> _state = new Dictionary<FillTupleKey, FillTuple>();
|
||||
readonly SetMap<uint, string> _order_fills = new SetMap<uint, string>();
|
||||
readonly SetMap<uint, string> _security_fills = new SetMap<uint, string>();
|
||||
IReadOnlyDictionary<string, FillImmutable> _fills_cache = new Dictionary<string, FillImmutable>(0);
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>(0);
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
|
||||
// _n_write_ops should never be greater than 1, bc all 3 input observables fire 'OnDispatcher'
|
||||
int _n_read_ops, _n_write_ops;
|
||||
IDisposable _dispose;
|
||||
|
||||
FillTupleSource(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) =>
|
||||
//
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) {
|
||||
State state = new State();
|
||||
var src = Observable.FromEvent<Action<TableUpdateTransform<FillTuple, string>>, TableUpdateTransform<FillTuple, string>>(cb => _updated += cb, cb => _updated -= cb);
|
||||
fill_tuples = Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
|
||||
var init_state = state.fill_tuples.state.ToDictionary();
|
||||
observer.OnNext(new TableUpdateTransform<FillTuple, string>(init_state, init_state.Values.ToList(), new List<FillTuple>(0)));
|
||||
return src.Subscribe(observer);
|
||||
});
|
||||
_dispose = Disposable.Create(
|
||||
securities.OnDispatcher().Subscribe(_update_securities),
|
||||
orders.OnDispatcher().Subscribe(_update_orders),
|
||||
fills.OnDispatcher().Subscribe(_update_fills));
|
||||
|
||||
public IDisposable Subscribe(IObserver<IIncrementalUpdate<FillTuple, FillTupleKey>> observer) {
|
||||
using (_read()) {
|
||||
observer.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(new Dictionary<FillTupleKey, FillTuple>(_state), new Dictionary<FillTupleKey, FillTuple>(0)));
|
||||
return _subject.Subscribe(observer);
|
||||
securities.ObserveOnDispatcher().Subscribe(s => update(state.update_securities(s))),
|
||||
orders.ObserveOnDispatcher().Subscribe(o => update(state.update_orders(o))),
|
||||
fills.ObserveOnDispatcher().Subscribe(f => update(state.update_fills(f)))
|
||||
);
|
||||
void update(State new_state) {
|
||||
state = new_state;
|
||||
_updated?.Invoke(state.fill_tuples);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
event Action<TableUpdateTransform<FillTuple, string>> _updated;
|
||||
|
||||
void _update_fills(TableUpdate<FillImmutable, string> fills) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> removed = new Dictionary<FillTupleKey, FillTuple>(), added = new Dictionary<FillTupleKey, FillTuple>();
|
||||
foreach (FillImmutable fill in fills.removed) {
|
||||
FillTuple old_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
_security_fills.remove(fill.SecurityKey, fill.FillKey);
|
||||
_order_fills.remove(fill.OrderKey, fill.FillKey);
|
||||
}
|
||||
foreach (FillImmutable fill in fills.added) {
|
||||
FillTuple new_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
|
||||
added.Add(new_ft.key, new_ft);
|
||||
_security_fills.add(fill.SecurityKey, fill.FillKey);
|
||||
_order_fills.add(fill.OrderKey, fill.FillKey);
|
||||
}
|
||||
_fills_cache = fills.state;
|
||||
_apply_changes(added, removed);
|
||||
IObservable<TableUpdateTransform<FillTuple, string>> fill_tuples { get; }
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
class State {
|
||||
readonly IReadOnlyDictionary<string, FillImmutable> _fills;
|
||||
readonly IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
|
||||
readonly IReadOnlyDictionary<uint, SecurityMD> _securities;
|
||||
readonly TableUpdateTransform<FillTuple, string> _fill_tuples;
|
||||
readonly SetMap<uint, string> _order_fills;
|
||||
readonly SetMap<uint, string> _security_fills;
|
||||
|
||||
public State() : this(
|
||||
new Dictionary<string, FillImmutable>(),
|
||||
new Dictionary<uint, OrderRecordImmutable>(),
|
||||
new Dictionary<uint, SecurityMD>(),
|
||||
new TableUpdateTransform<FillTuple, string>(new Dictionary<string, FillTuple>(), new List<FillTuple>(), new List<FillTuple>()),
|
||||
new SetMap<uint, string>(),
|
||||
new SetMap<uint, string>()
|
||||
) { }
|
||||
|
||||
State(
|
||||
IReadOnlyDictionary<string, FillImmutable> fills,
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> orders,
|
||||
IReadOnlyDictionary<uint, SecurityMD> securities,
|
||||
TableUpdateTransform<FillTuple, string> fill_tuples,
|
||||
SetMap<uint, string> order_fill_tuples,
|
||||
SetMap<uint, string> security_fill_tuples
|
||||
) {
|
||||
_fills = fills;
|
||||
_orders = orders;
|
||||
_securities = securities;
|
||||
_fill_tuples = fill_tuples;
|
||||
_order_fills = order_fill_tuples;
|
||||
_security_fills = security_fill_tuples;
|
||||
}
|
||||
}
|
||||
|
||||
void _update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
|
||||
HashSet<uint> changed_keys = new HashSet<uint>();
|
||||
foreach (var o in orders.removed) {
|
||||
if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD? sec_md = _get_security_md(o.security_key());
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple old_ft = new FillTuple(fill, o, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
}
|
||||
OrderRecordImmutable? curr_order = null;
|
||||
if (orders.try_get(o.Key, out OrderRecordImmutable newval)) {
|
||||
curr_order = newval;
|
||||
changed_keys.Add(o.Key);
|
||||
}
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple new_ft = new FillTuple(fill, curr_order, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
public TableUpdateTransform<FillTuple, string> fill_tuples => _fill_tuples;
|
||||
|
||||
public State update_fills(TableUpdate<FillImmutable, string> fills) {
|
||||
var removed = fills.removed.Select(get_fill_tuple).ToList();
|
||||
var added = fills.added.Select(get_fill_tuple).ToList();
|
||||
var order_fills = _order_fills.clone();
|
||||
var security_fills = _security_fills.clone();
|
||||
HashSet<string> added_keys = new HashSet<string>(fills.added.Select(f => f.FillKey)),
|
||||
removed_keys = new HashSet<string>(fills.removed.Select(f => f.FillKey));
|
||||
foreach (var ft in removed) {
|
||||
string k = ft.fill.FillKey;
|
||||
if (!added_keys.Contains(k)) {
|
||||
order_fills.remove(ft.fill.OrderKey, k);
|
||||
security_fills.remove(ft.fill.SecurityKey, k);
|
||||
}
|
||||
}
|
||||
foreach (var o in orders.added) {
|
||||
if (!changed_keys.Contains(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD? sec_md = _get_security_md(o.security_key());
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple old_ft = new FillTuple(fill, null, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, o, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
foreach (var ft in added) {
|
||||
string k = ft.fill.FillKey;
|
||||
if (!removed_keys.Contains(k)) {
|
||||
order_fills.add(ft.fill.OrderKey, k);
|
||||
security_fills.add(ft.fill.SecurityKey, k);
|
||||
}
|
||||
}
|
||||
_orders_cache = orders.state;
|
||||
_apply_changes(added, removed);
|
||||
return new State(fills.state, _orders, _securities, _fill_tuples.update(removed, added, f => f.key), order_fills, security_fills);
|
||||
//
|
||||
FillTuple get_fill_tuple(FillImmutable fill) =>
|
||||
new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(),
|
||||
_securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
}
|
||||
}
|
||||
|
||||
void _update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
|
||||
_security_md_cache.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
public State update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
|
||||
orders.detect_changed_keys(o => o.Key, out HashSet<uint> removed_keys, out HashSet<uint> added_keys, out HashSet<uint> changed_keys);
|
||||
foreach (var k in removed_keys) {
|
||||
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
|
||||
added.AddRange(get_fill_tuples(null, fill_keys));
|
||||
}
|
||||
}
|
||||
foreach (var k in added_keys) {
|
||||
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(null, fill_keys));
|
||||
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
|
||||
}
|
||||
}
|
||||
foreach (var k in changed_keys) {
|
||||
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
|
||||
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
|
||||
}
|
||||
}
|
||||
return new State(_fills, orders.state, _securities, _fill_tuples.update(removed, added, ft => ft.key), _order_fills, _security_fills);
|
||||
//
|
||||
IEnumerable<FillTuple> get_fill_tuples(OrderRecordImmutable? o, ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return new FillTuple(fill, o, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
});
|
||||
}
|
||||
|
||||
public State update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
|
||||
_securities.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
foreach (var k in removed_keys) {
|
||||
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD old_sec_md = _security_md_cache[k];
|
||||
foreach (FillImmutable fill in fills) {
|
||||
OrderRecordImmutable? order = _get_order(fill.OrderKey);
|
||||
FillTuple old_ft = new FillTuple(fill, order, old_sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, order, null);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
removed.AddRange(get_fill_tuples(_securities[k].security, fill_keys));
|
||||
added.AddRange(get_fill_tuples(null, fill_keys));
|
||||
}
|
||||
}
|
||||
foreach (var k in added_keys) {
|
||||
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD new_sec_md = securities[k];
|
||||
foreach (FillImmutable fill in fills) {
|
||||
OrderRecordImmutable? order = _get_order(fill.OrderKey);
|
||||
FillTuple old_ft = new FillTuple(fill, order, null);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, order, new_sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
removed.AddRange(get_fill_tuples(null, fill_keys));
|
||||
added.AddRange(get_fill_tuples(securities[k].security, fill_keys));
|
||||
}
|
||||
}
|
||||
_security_md_cache = securities;
|
||||
_apply_changes(added, removed);
|
||||
return new State(_fills, _orders, securities, _fill_tuples.update(removed, added, f => f.key), _order_fills, _security_fills);
|
||||
//
|
||||
IEnumerable<FillTuple> get_fill_tuples(SecurityImmutable? s, ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(), s);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SecurityMD? _get_security_md(uint key) =>
|
||||
_security_md_cache.TryGetValue(key, out SecurityMD smd) ? smd : new SecurityMD?();
|
||||
|
||||
OrderRecordImmutable? _get_order(uint key) =>
|
||||
_orders_cache.TryGetValue(key, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?();
|
||||
|
||||
void _apply_changes(Dictionary<FillTupleKey, FillTuple> added, Dictionary<FillTupleKey, FillTuple> removed) {
|
||||
foreach (var kv in removed)
|
||||
_state.Remove(kv.Key);
|
||||
foreach (var kv in added)
|
||||
_state.Add(kv.Key, kv.Value);
|
||||
_subject.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(added, removed));
|
||||
}
|
||||
|
||||
IDisposable _read() {
|
||||
while (_n_write_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_read_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
|
||||
}
|
||||
|
||||
IDisposable _write() {
|
||||
while (_n_read_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_write_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace mbts.spark {
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
props_source.ObserveOnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
|
||||
void on_props(InlineUserProperty<double> price_prop, InlineUserProperty<uint> size_prop) {
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace bts.spark {
|
||||
(var security, var spec) = get_security(neworder.Def.SecurityKey);
|
||||
//
|
||||
// description
|
||||
var mod_desc = modified_description(oldorder, neworder, spec);
|
||||
var mod_desc = modified_description(oldorder, neworder, spec.Value);
|
||||
if (!string.IsNullOrEmpty(mod_desc)) {
|
||||
log_status(ActivityLogCategory.modify, security, mod_desc);
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate StopLimitOrderTemplate { get; set; }
|
||||
public DataTemplate StopMarketOrderTemplate { get; set; }
|
||||
public DataTemplate TimeBoxOrderTemplate { get; set; }
|
||||
public DataTemplate TickOrderTemplate { get; set; }
|
||||
public DataTemplate LeanOrderTemplate { get; set; }
|
||||
public DataTemplate JoinOrderTemplate { get; set; }
|
||||
public DataTemplate TopOrderTemplate { get; set; }
|
||||
public DataTemplate IcebergOrderTemplate { get; set; }
|
||||
public DataTemplate SpreaderTemplate { get; set; }
|
||||
public DataTemplate BTSStopOrderTemplate { get; set; }
|
||||
@@ -37,10 +33,6 @@ namespace mbts.ui.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: return StopMarketOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return StopLimitOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: return TimeBoxOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: return TickOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: return LeanOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: return JoinOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top: return TopOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace mbts.spark {
|
||||
|
||||
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityImmutable> security_source, IObservable<InlineOrderProperties> order_properties) {
|
||||
_model = model;
|
||||
_security = backing(nameof(security), security_source.OnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
_security = backing(nameof(security), security_source.ObserveOnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
// guarentee that "this.security" will always be set before observers are notified
|
||||
// this is important bc some observers will reference "this.security" in their callbacks
|
||||
// - namely the [SelectorProperty]order_type's "get_default" function,
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace mbts.spark {
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
security_source.ObserveOnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.OnDispatcher()
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineJoinOrderProperties
|
||||
{
|
||||
public InlineJoinOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
var hedgetype = CustomOneofCase.Lean;
|
||||
if(!child_order_types.Contains(hedgetype))
|
||||
hedgetype = CustomOneofCase.Limit;
|
||||
|
||||
//ExceptionAssert.InvalidOperation.That(child_order_types.Contains(hedgetype), $"Hedge order type {hedgetype} is not in hedge_order_types");
|
||||
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
|
||||
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
|
||||
join_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
enable_hedge = new InlineUserProperty<bool>(false, false);
|
||||
hedge_ticks = new InlineUserProperty<int>(1, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
|
||||
|
||||
|
||||
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> join_threshold;
|
||||
|
||||
public readonly InlineUserProperty<bool> enable_hedge;
|
||||
public readonly InlineUserProperty<int> hedge_ticks;
|
||||
|
||||
}
|
||||
|
||||
class JoinOrderVM : OrderTypeVMBase<JoinOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineJoinOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
|
||||
IOrderTypeVM _hedge_order;
|
||||
IDisposable _hedge_order_errors_sub;
|
||||
|
||||
public uint tick_offset_on_deploy { get; set; } = 0;
|
||||
public bts.spark.QuantityMeasure join_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
bool _enable_hedge = false;
|
||||
public bool enable_hedge
|
||||
{
|
||||
get { return _enable_hedge; }
|
||||
set
|
||||
{
|
||||
_enable_hedge = value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
}
|
||||
public int hedge_ticks { get; set; } = 1;
|
||||
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineJoinOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
IObservable<InlineJoinOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
|
||||
{
|
||||
_select_hedge_order(p.selected_hedge_order_type.value);
|
||||
}
|
||||
if (p.tick_offset_on_deploy.force_override)
|
||||
{
|
||||
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
|
||||
RaisePropertyChanged(nameof(tick_offset_on_deploy));
|
||||
}
|
||||
if (p.join_threshold.force_override)
|
||||
{
|
||||
join_threshold = p.join_threshold.value;
|
||||
RaisePropertyChanged(nameof(join_threshold));
|
||||
}
|
||||
if (p.enable_hedge.force_override)
|
||||
{
|
||||
enable_hedge = p.enable_hedge.value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
if (p.hedge_ticks.force_override)
|
||||
{
|
||||
hedge_ticks = p.hedge_ticks.value;
|
||||
RaisePropertyChanged(nameof(hedge_ticks));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
if (_hedge_order == null && enable_hedge)
|
||||
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.join_order();
|
||||
i.TickOffsetOnDeploy = tick_offset_on_deploy;
|
||||
i.JoinThreshold = join_threshold;
|
||||
i.EnableHedge = enable_hedge;
|
||||
i.HedgeTicks = hedge_ticks;
|
||||
child_order.assign_to(i.Child);
|
||||
if(enable_hedge && hedge_order != null)
|
||||
hedge_order.assign_to(i.Hedge);
|
||||
value.Join = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> hedge_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM hedge_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _hedge_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _hedge_order_errors_sub);
|
||||
_hedge_order = value;
|
||||
_hedge_order?.init();
|
||||
if (_hedge_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
if (_hedge_order_choices != null)
|
||||
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
if (_hedge_order == null && _security.HasValue && enable_hedge)
|
||||
_select_hedge_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _select_hedge_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
|
||||
_inline_props.selected_hedge_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_hedge_order(ot);
|
||||
}
|
||||
|
||||
void _select_hedge_order(CustomOneofCase ot)
|
||||
{
|
||||
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_hedge_order == null)
|
||||
_hedge_order = hedge_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
|
||||
if ( (_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
|
||||
set_error(nameof(hedge_order), string.Empty, true);
|
||||
else clear_error(nameof(hedge_order));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineLeanOrderProperties
|
||||
{
|
||||
public InlineLeanOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(10, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
|
||||
public readonly InlineUserProperty<uint> max_chase_ticks;
|
||||
}
|
||||
|
||||
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineLeanOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineLeanOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
IObservable<InlineLeanOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (p.cross_threshold.force_override)
|
||||
{
|
||||
cross_threshold = p.cross_threshold.value;
|
||||
RaisePropertyChanged(nameof(cross_threshold));
|
||||
}
|
||||
if (p.max_chase_ticks.force_override)
|
||||
{
|
||||
max_chase_ticks = p.max_chase_ticks.value;
|
||||
RaisePropertyChanged(nameof(max_chase_ticks));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.lean_order();
|
||||
i.CrossThreshold = cross_threshold;
|
||||
i.MaxChaseTicks = max_chase_ticks;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Lean = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,6 @@ namespace mbts.spark {
|
||||
public static IOrderTypeVM stop(OrdersModel model) => new StopOrderVM(model);
|
||||
public static IOrderTypeVM stop_limit(OrdersModel model) => new StopLimitOrderVM(model);
|
||||
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TimeBoxOrderVM(model, security_source);
|
||||
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TickOrderVM(model, security_source);
|
||||
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityImmutable> security_source) => new LeanOrderVM(model, security_source);
|
||||
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityImmutable> security_source) => new JoinOrderVM(model, security_source);
|
||||
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TopOrderVM(model, security_source);
|
||||
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityImmutable> security_source) => new IcebergOrderVM(model, security_source);
|
||||
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source) => new SpreaderOrderVM(model, security_source);
|
||||
|
||||
@@ -31,10 +27,6 @@ namespace mbts.spark {
|
||||
case CustomOneofCase.Stop: return stop(model);
|
||||
case CustomOneofCase.StopLimit: return stop_limit(model);
|
||||
case CustomOneofCase.TimeBox: return time_box(model, security_source);
|
||||
case CustomOneofCase.Tick: return tick(model, security_source);
|
||||
case CustomOneofCase.Lean: return lean(model, security_source);
|
||||
case CustomOneofCase.Join: return join(model, security_source);
|
||||
case CustomOneofCase.Top: return top(model, security_source);
|
||||
case CustomOneofCase.Iceberg: return iceberg(model, security_source);
|
||||
case CustomOneofCase.Spreader: return spreader(model, security_source);
|
||||
default: throw new NotImplementedException();
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
private class SpreaderOrderVM : OrderTypeVMBase<SpreaderOrderDefinition>, IOrderTypeVM, IDisposable {
|
||||
readonly Property<SpreaderOrderDefinitionImmutable?> _template;
|
||||
readonly StrategyEditorWrapper _strategy_editor_wrapper;
|
||||
IDisposable _dispose;
|
||||
SyntheticEditorVM _strategy_editor;
|
||||
|
||||
public SpreaderOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.SelectMany(s => model.spreader.security_strategy(s.Id)
|
||||
@@ -28,15 +26,15 @@ namespace mbts.spark {
|
||||
IObservable<InlineUserProperty<SpreaderOrderDefinitionImmutable>> order_template_source)
|
||||
: base(model) {
|
||||
_template = backing(nameof(template), new SpreaderOrderDefinitionImmutable?());
|
||||
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
|
||||
_dispose = Disposable.Create(
|
||||
_strategy_editor_wrapper,
|
||||
_template.OnDispatcher().Subscribe(vals => {
|
||||
_template.ObserveOnDispatcher().Subscribe(vals => {
|
||||
_validate();
|
||||
}),
|
||||
order_template_source.OnDispatcher().Subscribe(prop => {
|
||||
order_template_source.ObserveOnDispatcher().Subscribe(prop => {
|
||||
if (prop.force_override || (!template.HasValue && prop.has_value)) {
|
||||
_template.Value = prop.value;
|
||||
if (_strategy_editor != null)
|
||||
_strategy_editor.set_synthetic(prop.value.Security, prop.value.ExecutionStrategy);
|
||||
_validate();
|
||||
}
|
||||
})
|
||||
@@ -45,14 +43,23 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public SpreaderOrderDefinitionImmutable? template => _template.Value;
|
||||
public SyntheticEditorVM strategy_editor => _strategy_editor_wrapper.vm;
|
||||
public IEnumerable<DialogSkirtItem> skirt_items => _strategy_editor_wrapper.skirt_items;
|
||||
|
||||
public SyntheticEditorVM strategy_editor {
|
||||
get {
|
||||
if (_strategy_editor == null && template.HasValue) {
|
||||
var def = template.Value;
|
||||
_strategy_editor = new SyntheticEditorVM(model.spreader, def.Security, def.ExecutionStrategy, model.security_has_working_orders(def.Security.Id));
|
||||
}
|
||||
return _strategy_editor;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool editable_properties_open {
|
||||
get => base.editable_properties_open;
|
||||
set {
|
||||
if (!value && template.HasValue && strategy_editor != null) {
|
||||
if (!value && template.HasValue && _strategy_editor != null) {
|
||||
var mutable_template = template.Value.CloneToMutable();
|
||||
mutable_template.ExecutionStrategy = _strategy_editor_wrapper.vm.get_strategy().CloneToMutable();
|
||||
mutable_template.ExecutionStrategy = _strategy_editor.get_strategy().CloneToMutable();
|
||||
_template.Value = new SpreaderOrderDefinitionImmutable(mutable_template);
|
||||
}
|
||||
base.editable_properties_open = value;
|
||||
@@ -73,39 +80,6 @@ namespace mbts.spark {
|
||||
set_error(nameof(template), "Waiting for security and/or strategy.", true);
|
||||
else clear_error(nameof(template));
|
||||
}
|
||||
|
||||
// lazily initializes SyntheticEditorVM and associated dialog skirt items
|
||||
class StrategyEditorWrapper : IDisposable {
|
||||
readonly SpreaderOrderVM _owner;
|
||||
SyntheticEditorVM _vm;
|
||||
DialogSkirtItem _save_strategy_item;
|
||||
|
||||
public StrategyEditorWrapper(SpreaderOrderVM owner) {
|
||||
_owner = owner;
|
||||
_owner._template.Changed += _order_template_changed;
|
||||
}
|
||||
|
||||
public SyntheticEditorVM vm { get { _init(); return _vm; } }
|
||||
public IEnumerable<DialogSkirtItem> skirt_items { get { _init(); yield return _save_strategy_item; } }
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
_owner._template.Changed -= _order_template_changed;
|
||||
}
|
||||
|
||||
void _init() {
|
||||
if (_vm == null && _owner.template.HasValue) {
|
||||
var def = _owner.template.Value;
|
||||
_vm = new SyntheticEditorVM(_owner.model.spreader, def.Security, def.ExecutionStrategy, _owner.model.security_has_working_orders(def.Security.Id));
|
||||
_save_strategy_item = new DialogSkirtItem("Save Strategy", _vm.commit);
|
||||
}
|
||||
}
|
||||
|
||||
void _order_template_changed(SpreaderOrderDefinitionImmutable? oldval, SpreaderOrderDefinitionImmutable? newval) {
|
||||
if (_vm != null && newval.HasValue)
|
||||
_vm.set_synthetic(newval.Value.Security, newval.Value.ExecutionStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineTickOrderProperties
|
||||
{
|
||||
public InlineTickOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
cross_ticks = new InlineUserProperty<uint>(0, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(10, false);
|
||||
detach_on_toward = new InlineUserProperty<bool>(true, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
|
||||
public readonly InlineUserProperty<uint> cross_ticks;
|
||||
public readonly InlineUserProperty<uint> max_chase_ticks;
|
||||
public readonly InlineUserProperty<bool> detach_on_toward;
|
||||
}
|
||||
|
||||
class TickOrderVM : OrderTypeVMBase<TickOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTickOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public uint tick_offset_on_deploy { get; set; } = 0;
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint cross_ticks { get; set; } = 0;
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
public bool detach_on_toward { get; set; } = true;
|
||||
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineTickOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
IObservable<InlineTickOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (p.tick_offset_on_deploy.force_override)
|
||||
{
|
||||
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
|
||||
RaisePropertyChanged(nameof(tick_offset_on_deploy));
|
||||
}
|
||||
if (p.cross_threshold.force_override)
|
||||
{
|
||||
cross_threshold = p.cross_threshold.value;
|
||||
RaisePropertyChanged(nameof(cross_threshold));
|
||||
}
|
||||
if (p.cross_ticks.force_override)
|
||||
{
|
||||
cross_ticks = p.cross_ticks.value;
|
||||
RaisePropertyChanged(nameof(cross_ticks));
|
||||
}
|
||||
if (p.max_chase_ticks.force_override)
|
||||
{
|
||||
max_chase_ticks = p.max_chase_ticks.value;
|
||||
RaisePropertyChanged(nameof(max_chase_ticks));
|
||||
}
|
||||
if (p.detach_on_toward.force_override)
|
||||
{
|
||||
detach_on_toward = p.detach_on_toward.value;
|
||||
RaisePropertyChanged(nameof(detach_on_toward));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.tick_order();
|
||||
i.TickOffsetOnDeploy = tick_offset_on_deploy;
|
||||
i.CrossThreshold = cross_threshold;
|
||||
i.CrossTicks = cross_ticks;
|
||||
i.MaxChaseTicks = max_chase_ticks;
|
||||
i.DetachOnToward = detach_on_toward;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Tick = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,9 @@ namespace mbts.spark {
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
security_source.ObserveOnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.OnDispatcher()
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineTopOrderProperties
|
||||
{
|
||||
public InlineTopOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
var hedgetype = CustomOneofCase.Lean;
|
||||
if(!child_order_types.Contains(hedgetype))
|
||||
hedgetype = CustomOneofCase.Limit;
|
||||
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
|
||||
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
watch_level_tick_offset = new InlineUserProperty<uint>(0, false);
|
||||
top_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 }, false);
|
||||
enable_hedge = new InlineUserProperty<bool>(false, false);
|
||||
hedge_ticks = new InlineUserProperty<int>(1, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
|
||||
|
||||
|
||||
public readonly InlineUserProperty<uint> watch_level_tick_offset;
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> top_threshold;
|
||||
|
||||
public readonly InlineUserProperty<bool> enable_hedge;
|
||||
public readonly InlineUserProperty<int> hedge_ticks;
|
||||
|
||||
}
|
||||
|
||||
class TopOrderVM : OrderTypeVMBase<TopOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTopOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
|
||||
IOrderTypeVM _hedge_order;
|
||||
IDisposable _hedge_order_errors_sub;
|
||||
|
||||
public uint watch_level_tick_offset { get; set; } = 0;
|
||||
public bts.spark.QuantityMeasure top_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
bool _enable_hedge = false;
|
||||
public bool enable_hedge
|
||||
{
|
||||
get { return _enable_hedge; }
|
||||
set
|
||||
{
|
||||
_enable_hedge = value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
}
|
||||
public int hedge_ticks { get; set; } = 1;
|
||||
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineTopOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList())))
|
||||
{ }
|
||||
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
|
||||
IObservable<InlineTopOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p =>
|
||||
{
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
|
||||
{
|
||||
_select_hedge_order(p.selected_hedge_order_type.value);
|
||||
}
|
||||
if (p.watch_level_tick_offset.force_override)
|
||||
{
|
||||
watch_level_tick_offset = p.watch_level_tick_offset.value;
|
||||
RaisePropertyChanged(nameof(watch_level_tick_offset));
|
||||
}
|
||||
if (p.top_threshold.force_override)
|
||||
{
|
||||
top_threshold = p.top_threshold.value;
|
||||
RaisePropertyChanged(nameof(top_threshold));
|
||||
}
|
||||
if (p.enable_hedge.force_override)
|
||||
{
|
||||
enable_hedge = p.enable_hedge.value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
if (p.hedge_ticks.force_override)
|
||||
{
|
||||
hedge_ticks = p.hedge_ticks.value;
|
||||
RaisePropertyChanged(nameof(hedge_ticks));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
if (_hedge_order == null && enable_hedge)
|
||||
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.top_order();
|
||||
i.WatchLevelTickOffset = watch_level_tick_offset;
|
||||
i.TopThreshold = top_threshold;
|
||||
i.EnableHedge = enable_hedge;
|
||||
i.HedgeTicks = hedge_ticks;
|
||||
child_order.assign_to(i.Child);
|
||||
if (enable_hedge && hedge_order != null)
|
||||
hedge_order.assign_to(i.Hedge);
|
||||
value.Top = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> hedge_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM hedge_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _hedge_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _hedge_order_errors_sub);
|
||||
_hedge_order = value;
|
||||
_hedge_order?.init();
|
||||
if (_hedge_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
if (_hedge_order_choices != null)
|
||||
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
if (_hedge_order == null && _security.HasValue && enable_hedge)
|
||||
_select_hedge_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _select_hedge_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
|
||||
_inline_props.selected_hedge_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_hedge_order(ot);
|
||||
}
|
||||
|
||||
void _select_hedge_order(CustomOneofCase ot)
|
||||
{
|
||||
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_hedge_order == null)
|
||||
_hedge_order = hedge_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
|
||||
if ((_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
|
||||
set_error(nameof(hedge_order), string.Empty, true);
|
||||
else clear_error(nameof(hedge_order));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,11 @@ namespace mbts.spark {
|
||||
_service = service;
|
||||
_spreader = spreader;
|
||||
_error = error;
|
||||
on_orders = _service.subscribei_orders().Table().Cache();
|
||||
on_orders = _service.subscribei_orders().Table().Replay(1).RefCount();
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> empty_orders = new Dictionary<uint, OrderRecordImmutable>();
|
||||
working_orders =
|
||||
on_orders.Scan(empty_orders, (worders, table) => _update_working_orders(worders, table)).Replay(1).RefCount();
|
||||
on_fills = _service.subscribei_fills().Table().Cache();
|
||||
on_fills = _service.subscribei_fills().Table().Replay(1).RefCount();
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
@@ -53,7 +53,7 @@ namespace mbts.spark {
|
||||
public SpreaderModel spreader => _spreader;
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
|
||||
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
|
||||
public IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> on_fill_tuples { get; }
|
||||
public IObservable<ITableUpdateTransform<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
|
||||
|
||||
public IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
Assert.IsNotNull(Application.Current);
|
||||
Assert.IsNotNull(Application.Current.Windows);
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
|
||||
// an ISlowWindow gets its own thread
|
||||
|
||||
@@ -175,8 +175,8 @@ namespace bts.spark
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
|
||||
_tag = tag;
|
||||
_subs = Disposable.Create(
|
||||
parameter.OnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.OnDispatcher().Subscribe(ce => {
|
||||
parameter.ObserveOnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.ObserveOnDispatcher().Subscribe(ce => {
|
||||
_can_execute = ce;
|
||||
raiseCanExecuteChanged();
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.SparkBusData;
|
||||
|
||||
namespace mbts.spark
|
||||
@@ -35,8 +34,6 @@ namespace mbts.spark
|
||||
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
|
||||
|
||||
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
|
||||
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
|
||||
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
|
||||
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
|
||||
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
|
||||
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
|
||||
|
||||
@@ -199,10 +199,10 @@ namespace mbts.ui {
|
||||
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
|
||||
|
||||
// Order Types
|
||||
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor lean_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor join_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor top_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor tick_order { get { return new BTSColor(31, 4, 57);}}
|
||||
public static BTSColor lean_order { get { return new BTSColor(110, 77, 143);}}
|
||||
public static BTSColor join_order { get { return new BTSColor(15, 1, 29);}}
|
||||
public static BTSColor leader_order { get { return new BTSColor(40, 9, 72);}}
|
||||
}
|
||||
|
||||
public static class AW2 {
|
||||
|
||||
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
|
||||
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
yield return item;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
|
||||
@@ -345,17 +345,6 @@ namespace System.Reactive.Linq
|
||||
|
||||
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
|
||||
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
|
||||
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
|
||||
}
|
||||
|
||||
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
|
||||
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,33 +54,5 @@
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
|
||||
if (args == null || args.Length != 3) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
|
||||
if (args == null || args.Length != 4) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
arg4 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
rv |= try_convert(args[3], out arg4);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,8 @@ namespace mbts {
|
||||
public static double tick_unit_frac(this TickFactors tick) =>
|
||||
tick.tick_unit / tick.tick_base;
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => factor_out(tick.tick_base, 10);
|
||||
|
||||
public static string format(this TickFactors tick, double value) =>
|
||||
format(tick, value, double.NaN, false, _nan_text);
|
||||
format(tick, value, double.NaN, true, _nan_text);
|
||||
|
||||
public static string format(this TickFactors tick, double value, bool show_commas) =>
|
||||
format(tick, value, double.NaN, show_commas, _nan_text);
|
||||
@@ -178,10 +176,9 @@ namespace mbts {
|
||||
if (DoubleHelper.IsNaNOrInfinity(input)) return input;
|
||||
return input <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick.round(input) : regional_tick.high_tick.round(input);
|
||||
}
|
||||
public static int display_digits(this TickFactors tick) => display_digits(tick.tick_base);
|
||||
#endregion
|
||||
|
||||
public static int display_digits(double tick_base) {
|
||||
public static int n_display_digits(double tick_base) {
|
||||
double log_tick_base = Math.Log10(tick_base);
|
||||
int floor = (int)Math.Floor(log_tick_base);
|
||||
double dec = log_tick_base - floor;
|
||||
@@ -219,7 +216,7 @@ namespace mbts {
|
||||
if (numerator_part >= tick_base) numerator_part /= Math.Pow(10, Math.Ceiling(Math.Log10(numerator_part) - Math.Log10(tick_base)));
|
||||
else {
|
||||
//e.g [tick_base 640] 1989-8 == 1989-80, or 1989-32 == 1989-320
|
||||
int ndd_diff = display_digits(tick_base) - numerator_text.Length;
|
||||
int ndd_diff = n_display_digits(tick_base) - numerator_text.Length;
|
||||
if (ndd_diff > 0) numerator_part *= Math.Pow(10, ndd_diff);
|
||||
if (numerator_part >= tick_base) numerator_part /= 10;
|
||||
}
|
||||
@@ -258,21 +255,16 @@ namespace mbts {
|
||||
double numerator_dec = value - integer_part;
|
||||
double unit_frac = tick.tick_unit / tick.tick_base;
|
||||
int n_units = (int)Math.Round(numerator_dec / unit_frac);
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', display_digits(tick.tick_base));
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', n_display_digits(tick.tick_base));
|
||||
double decimal_part = n_units * tick.tick_unit;
|
||||
if (decimal_part == tick.tick_base) {
|
||||
++integer_part;
|
||||
decimal_part = 0;
|
||||
} else {
|
||||
// some ticks are not formatted with 100% precision, using less than the required display_digits
|
||||
// for such cases, we need to truncate the decimal part rather than round it.
|
||||
decimal_part = Math.Floor(decimal_part);
|
||||
}
|
||||
string integer_part_text = show_commas ? integer_part.ToString("#,0") : integer_part.ToString();
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', display_digits(tick.tick_base)));
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', n_display_digits(tick.tick_base)));
|
||||
}
|
||||
}
|
||||
catch (OverflowException ex) {
|
||||
} catch (OverflowException ex) {
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
|
||||
return "Overflow";
|
||||
}
|
||||
@@ -280,7 +272,7 @@ namespace mbts {
|
||||
|
||||
private static double _round_tick(double tick_unit_frac, double input) {
|
||||
return input - Math.IEEERemainder(input, tick_unit_frac);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -69,18 +69,16 @@ namespace mbts.ui.settings
|
||||
if (window == null || window.Owner == null) return null;
|
||||
MemoryStream out_stream = new MemoryStream();
|
||||
window.GetState(out_stream);
|
||||
|
||||
var pos = _interop.GetPosition(window);
|
||||
var state = new WindowState() {
|
||||
owner = window.Owner.key(),
|
||||
app_data = out_stream.ToArray(),
|
||||
position = pos,
|
||||
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
|
||||
position = _interop.GetPosition(window),
|
||||
title = window.Title
|
||||
};
|
||||
return state;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ using bts.api.persistence;
|
||||
|
||||
namespace mbts.ui.settings
|
||||
{
|
||||
//
|
||||
// This class is used for tools like SettingsManager.exe
|
||||
//
|
||||
[Export(typeof(IUserPersistFactory))]
|
||||
class UISettingsFactory : IUserPersistFactory
|
||||
{
|
||||
@@ -37,7 +34,7 @@ namespace mbts.ui.settings
|
||||
|
||||
public IUserPersist get_persist_for(string name) {
|
||||
var user = _users.Result.FirstOrDefault(u => u.Login == name);
|
||||
return new UserUISettings(user.Id, _persist, null);
|
||||
return new UserUISettings(user.Id, _persist);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,6 @@ namespace mbts.ui.shell {
|
||||
[ProtoMember(102), DefaultValue(true)]
|
||||
public bool always_on_top = true;
|
||||
//[ProtoMember(103)] //used
|
||||
|
||||
[ProtoMember(104)]
|
||||
public string custom_title;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -61,7 +58,7 @@ namespace mbts.ui.shell {
|
||||
public WindowPosition position;
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string title; // for debugging
|
||||
public string title;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace mbts.ui
|
||||
Task<IShellWindow> create(Stream load_stream);
|
||||
}
|
||||
|
||||
// ILazyModule may also impement IShellMenu to get opening/closing notification
|
||||
public interface ILazyMenuModule : IShellModule { }
|
||||
|
||||
public interface IPersistShellModule : IShellModule {
|
||||
// Called at startup before the UI is shown
|
||||
// This is an opportunity for you to kick off background services
|
||||
|
||||
@@ -17,5 +17,5 @@ namespace mbts.ui
|
||||
WindowPosition GetPosition(IShellWindow window);
|
||||
bool IsVisible(IShellWindow window);
|
||||
void PinWindow(IShellWindow window);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using bts.api.core;
|
||||
using bts.api.persistence;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reactive.Linq;
|
||||
using bts.spark;
|
||||
|
||||
namespace mbts.persist
|
||||
{
|
||||
@@ -19,13 +18,11 @@ namespace mbts.persist
|
||||
protected abstract string user();
|
||||
string uns(string ns) { return $"{user()}#{ns}"; }
|
||||
PersistenceService _persist;
|
||||
ErrorReporter _errors;
|
||||
|
||||
private const string _settings_change_base = "/ui/settingschange/";
|
||||
|
||||
public UISettingsBase(PersistenceService persist, ErrorReporter errors) {
|
||||
public UISettingsBase(PersistenceService persist) {
|
||||
_persist = persist;
|
||||
_errors = errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +50,7 @@ namespace mbts.persist
|
||||
Ns = uns(ns),
|
||||
Key = { key }
|
||||
});
|
||||
check_error(ret, ns, key, out bool empty);
|
||||
check_error(ret.value.Result, ns, key, out bool empty);
|
||||
if (empty) return default(T);
|
||||
return deserialize<T>(ret.value.Record.Single().Value);
|
||||
}
|
||||
@@ -70,28 +67,20 @@ namespace mbts.persist
|
||||
return ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(stream, ProtoBuf.PrefixStyle.Fixed32);
|
||||
}
|
||||
|
||||
private void check_error(Result<RetrieveResponse> result, string ns, string key, out bool empty) {
|
||||
if (result.success) {
|
||||
switch (result.value.Result) {
|
||||
case RetrieveResponse.Types.Result.EmptyNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchKey:
|
||||
empty = true;
|
||||
return;
|
||||
case RetrieveResponse.Types.Result.EmptyKey:
|
||||
case RetrieveResponse.Types.Result.DuplicateKey:
|
||||
_errors?.report_error($"Invalid Key {result} '{key}'", "Requested Invalid Settings Key", System.Windows.MessageBoxImage.Information);
|
||||
empty = true;
|
||||
return;
|
||||
default:
|
||||
case RetrieveResponse.Types.Result.Ok:
|
||||
empty = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_errors?.report_error($"There was an error communicating with the settings server: '{result.error_message}'", "Failed to retrieve settings from the server");
|
||||
empty = true;
|
||||
private void check_error(RetrieveResponse.Types.Result result, string ns, string key, out bool empty) {
|
||||
switch (result) {
|
||||
case RetrieveResponse.Types.Result.EmptyNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchKey:
|
||||
empty = true;
|
||||
return;
|
||||
case RetrieveResponse.Types.Result.EmptyKey:
|
||||
case RetrieveResponse.Types.Result.DuplicateKey:
|
||||
throw new ArgumentException($"Invalid Key {result} '{key}'");
|
||||
default:
|
||||
case RetrieveResponse.Types.Result.Ok:
|
||||
empty = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +90,7 @@ namespace mbts.persist
|
||||
new RetrieveAllRequest() {
|
||||
Ns = uns(ns)
|
||||
});
|
||||
check_error(results, ns, "", out bool empty);
|
||||
check_error(results.value.Result, ns, "", out bool empty);
|
||||
if (empty) return Enumerable.Empty<PersistResult<T>>();
|
||||
return results.value.Record
|
||||
.Where(r => re.IsMatch(r.Key))
|
||||
@@ -142,7 +131,7 @@ namespace mbts.persist
|
||||
protected override string user() { return "all_users"; }
|
||||
|
||||
[ImportingConstructor]
|
||||
public UISettings([Import("settings")]PersistenceService p, ErrorReporter err) : base(p,err) { }
|
||||
public UISettings([Import("settings")]PersistenceService p) : base(p) { }
|
||||
}
|
||||
|
||||
[Export(typeof(IUserPersist))]
|
||||
@@ -151,8 +140,8 @@ namespace mbts.persist
|
||||
string _user;
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist)
|
||||
: base(persist) {
|
||||
_user = $"User {user}";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
public static class EqualityComparer {
|
||||
const int _hash_seed = 63607, _hash_multiplier = 6451;
|
||||
|
||||
public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals) =>
|
||||
new AnonymousEqualityComparer<T>(equals);
|
||||
|
||||
@@ -26,40 +23,12 @@ namespace mbts.utils {
|
||||
public static IEqualityComparer<T?> CreateNullable<T>(Func<T, T, bool> equals) where T : struct =>
|
||||
new AnonymousEqualityComparer<T?>((a, b) => a.HasValue ? b.HasValue && equals(a.Value, b.Value) : !b.HasValue);
|
||||
|
||||
public static IEqualityComparer to_non_generic<T>(this IEqualityComparer<T> comparer) =>
|
||||
new AnonymousEqualityComparer<T>(comparer.Equals, comparer.GetHashCode);
|
||||
|
||||
public static bool NullCheckEquals<T>(T x, T y, Func<T, T, bool> non_null_equals) where T : class, IEquatable<T> =>
|
||||
x == null ? y == null :
|
||||
y != null &&
|
||||
non_null_equals(x, y);
|
||||
|
||||
public static int aggregate_hash(int item1_hash, int item2_hash, int item3_hash) {
|
||||
int rv = _hash_seed;
|
||||
unchecked {
|
||||
_accumulate_hash(ref rv, ref item1_hash);
|
||||
_accumulate_hash(ref rv, ref item2_hash);
|
||||
_accumulate_hash(ref rv, ref item3_hash);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Note: be wary of the performance cost this method may have, as frequently creating large arrays can be expensive.
|
||||
// If you wish to avoid this cost, consider making an overload like the method above, with the appropriate number of item-hash arguments
|
||||
public static int aggregate_hash(params int[] item_hashes) {
|
||||
int hash = _hash_seed;
|
||||
unchecked {
|
||||
foreach (int item_hash in item_hashes)
|
||||
hash = hash * _hash_multiplier + item_hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// WARNING: be sure to call this within in an unchecked scope
|
||||
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
|
||||
aggregate_hash = aggregate_hash * _hash_multiplier + item_hash;
|
||||
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T>, IEqualityComparer {
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
|
||||
readonly Func<T, T, bool> _equals;
|
||||
readonly Func<T, int> _get_hash_code;
|
||||
|
||||
@@ -71,9 +40,6 @@ namespace mbts.utils {
|
||||
|
||||
public bool Equals(T x, T y) => _equals(x, y);
|
||||
public int GetHashCode(T obj) => _get_hash_code(obj);
|
||||
|
||||
bool IEqualityComparer.Equals(object x, object y) => x is T xt && y is T yt && _equals(xt, yt);
|
||||
int IEqualityComparer.GetHashCode(object obj) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
public static class CompareUtils {
|
||||
@@ -21,22 +19,5 @@ namespace mbts.utils {
|
||||
b.HasValue ? compare(a.Value, b.Value) : 1 :
|
||||
b.HasValue ? -1 : 0;
|
||||
#endregion
|
||||
|
||||
#region Comparer
|
||||
public static IComparer to_non_generic<T>(this IComparer<T> comparer) =>
|
||||
new NonGenericComparerWrapper<T>(comparer);
|
||||
|
||||
public static IComparer<TOutter> wrap<TInner, TOutter>(this IComparer<TInner> inner_comparer, Func<TOutter, TInner> selector) =>
|
||||
Comparer<TOutter>.Create((a, b) => inner_comparer.Compare(a == null ? default : selector(a), b == null ? default : selector(b)));
|
||||
|
||||
class NonGenericComparerWrapper<T> : IComparer {
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
public NonGenericComparerWrapper(IComparer<T> comparer) => _comparer = comparer;
|
||||
|
||||
public int Compare(object x, object y) =>
|
||||
_comparer.Compare(ObjectHelper.try_convert<T>(x), ObjectHelper.try_convert<T>(y));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace mbts.spark {
|
||||
interface IValueFactoryVM<T> {
|
||||
T create();
|
||||
void load_from(T value);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.utils {
|
||||
interface IObservableDemuxer<TKey, in TInput, out TOutput> : IObserver<TInput> {
|
||||
@@ -89,17 +89,21 @@ namespace mbts.utils {
|
||||
_group_key = group_key;
|
||||
}
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public IObservable<ITableUpdateTransform<T, TKey>> subscribe(TGroupKey group_key) =>
|
||||
Observable.Create<ITableUpdateTransform<T, TKey>>(o => {
|
||||
var data = _map.GetOrAdd(group_key, gk => new Data());
|
||||
using (data.read()) {
|
||||
o.OnNext(data.current.as_snapshot());
|
||||
return data.subject.Subscribe(o);
|
||||
lock (data) {
|
||||
var current = data.current;
|
||||
if (current.state.Count > 0) {
|
||||
var state_clone = current.state.ToDictionary();
|
||||
o.OnNext(new TableUpdateTransform<T, TKey>(state_clone, state_clone.Values.ToList(), new List<T>(0)));
|
||||
}
|
||||
}
|
||||
return data.subject.Subscribe(o);
|
||||
});
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public void OnNext(TableUpdate<T, TKey> table) {
|
||||
var changes = new Dictionary<TGroupKey, ChangeSet>();
|
||||
foreach (T value in table.added)
|
||||
@@ -112,29 +116,16 @@ namespace mbts.utils {
|
||||
}
|
||||
|
||||
Data _update(Data data, ChangeSet changes) {
|
||||
using (data.write())
|
||||
data.subject.OnNext(data.current = data.current.update(changes.removed, changes.added, _key));
|
||||
lock (data) {
|
||||
data.current = data.current.update(changes.removed, changes.added, _key);
|
||||
data.subject.OnNext(data.current);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
class Data {
|
||||
int _n_read_ops, _n_write_ops;
|
||||
public readonly Subject<TableUpdateTransform<T, TKey>> subject = new Subject<TableUpdateTransform<T, TKey>>();
|
||||
public TableUpdateTransform<T, TKey> current = new TableUpdateTransform<T, TKey>();
|
||||
|
||||
public IDisposable read() {
|
||||
while (_n_write_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_read_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
|
||||
}
|
||||
|
||||
public IDisposable write() {
|
||||
while (_n_read_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_write_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
|
||||
}
|
||||
}
|
||||
|
||||
// [John]I prefer this class over anonymous tuples bc it is easy to mix up Item1/Item2
|
||||
|
||||
@@ -7,30 +7,12 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.utils {
|
||||
interface IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
IReadOnlyDictionary<TKey, T> added { get; }
|
||||
IReadOnlyDictionary<TKey, T> removed { get; }
|
||||
}
|
||||
|
||||
interface ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
IReadOnlyDictionary<TKey, T> state { get; }
|
||||
IReadOnlyList<T> added { get; }
|
||||
IReadOnlyList<T> removed { get; }
|
||||
}
|
||||
|
||||
class IncrementalUpdate<T, TKey> : IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public IncrementalUpdate()
|
||||
: this(new Dictionary<TKey, T>(0), new Dictionary<TKey, T>(0)) { }
|
||||
|
||||
public IncrementalUpdate(IReadOnlyDictionary<TKey, T> added, IReadOnlyDictionary<TKey, T> removed) {
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<TKey, T> added { get; }
|
||||
public IReadOnlyDictionary<TKey, T> removed { get; }
|
||||
}
|
||||
|
||||
class TableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
readonly Dictionary<TKey, T> _state;
|
||||
readonly IReadOnlyList<T> _added, _removed;
|
||||
@@ -62,18 +44,6 @@ namespace mbts.utils {
|
||||
table._state.Add(key(item), item);
|
||||
return table;
|
||||
}
|
||||
|
||||
public TableUpdateTransform<T, TKey> update(IIncrementalUpdate<T, TKey> update, Func<T, TKey> key) {
|
||||
var table = new TableUpdateTransform<T, TKey>(new Dictionary<TKey, T>(_state), added, removed);
|
||||
foreach (var k in update.removed.Keys)
|
||||
table._state.Remove(k);
|
||||
foreach (var kv in update.added)
|
||||
table._state.Add(kv.Key, kv.Value);
|
||||
return table;
|
||||
}
|
||||
|
||||
internal TableUpdateTransform<T, TKey> as_snapshot() =>
|
||||
new TableUpdateTransform<T, TKey>(_state, _state.Values.ToList(), new T[0]);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
@@ -88,15 +58,6 @@ namespace mbts.utils {
|
||||
.Select(u => new FilteredTableUpdateTransform<T, TKey>(u, predicate))
|
||||
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a 1:1 conversion to an IncrementalUpdate sequence of TResult (TKey remains the same).
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<TResult, TKey>> convert<T, TKey, TResult>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
source.Select(update => new IncrementalUpdate<TResult, TKey>(
|
||||
update.added.ToDictionary(kv => kv.Key, kv => selector(kv.Value)),
|
||||
update.removed.ToDictionary(kv => kv.Key, kv => selector(kv.Value))));
|
||||
|
||||
/// <summary>
|
||||
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
|
||||
/// </summary>
|
||||
@@ -108,33 +69,6 @@ namespace mbts.utils {
|
||||
source.Scan(new PivotTableUpdateTransform<T, TKey, TResult, TResultKey>(selector, result_key, key),
|
||||
(pivot, table) => pivot.update(table));
|
||||
|
||||
/// <summary>
|
||||
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<TResult, TResultKey>> with_respect_to<T, TKey, TResult, TResultKey>(
|
||||
this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key)
|
||||
where TKey : IEquatable<TKey>
|
||||
where TResultKey : IEquatable<TResultKey> =>
|
||||
//
|
||||
source.Scan(new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(selector, result_key, key),
|
||||
(pivot, table) => pivot.update(table));
|
||||
|
||||
/// <summary>
|
||||
/// Uses a Scan operator to accumulate state in the resulting ITableUpdateTransform sequence.
|
||||
/// </summary>
|
||||
public static IObservable<ITableUpdateTransform<T, TKey>> table<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TKey> key) where TKey : IEquatable<TKey> =>
|
||||
source.Scan(
|
||||
new TableUpdateTransform<T, TKey>(),
|
||||
(table, update) => table.update(update, key));
|
||||
|
||||
/// <summary>
|
||||
/// Filters the incremental added/removed lists by a predicate. Observers are only notified when the filtered added/removed lists have elements.
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<T, TKey>> filter<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, bool> predicate)
|
||||
where TKey : IEquatable<TKey> => source
|
||||
.Select(u => new IncrementalUpdate<T, TKey>(u.added.Where(kv => predicate(kv.Value)).ToDictionary(), u.removed.Where(kv => predicate(kv.Value)).ToDictionary()))
|
||||
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
|
||||
|
||||
class FilteredTableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
internal FilteredTableUpdateTransform(ITableUpdateTransform<T, TKey> source, Func<T, bool> predicate) {
|
||||
state = source.state;
|
||||
@@ -147,57 +81,6 @@ namespace mbts.utils {
|
||||
public IReadOnlyList<T> removed { get; }
|
||||
}
|
||||
|
||||
class PivotIncrementalUpdate<T, TKey, TResult, TResultKey> : IIncrementalUpdate<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
|
||||
readonly Dictionary<TResultKey, TResult> _state;
|
||||
readonly SetMap<TResultKey, TKey> _key_map;
|
||||
readonly Func<T, TResult> _selector;
|
||||
readonly Func<TResult, TResultKey> _result_key;
|
||||
readonly Func<T, TKey> _key;
|
||||
|
||||
internal PivotIncrementalUpdate(Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) :
|
||||
this(new Dictionary<TResultKey, TResult>(), new Dictionary<TResultKey, TResult>(0), new Dictionary<TResultKey, TResult>(0), new SetMap<TResultKey, TKey>(), selector, result_key, key) { }
|
||||
|
||||
PivotIncrementalUpdate(Dictionary<TResultKey, TResult> state, IReadOnlyDictionary<TResultKey, TResult> added, IReadOnlyDictionary<TResultKey, TResult> removed,
|
||||
SetMap<TResultKey, TKey> key_map, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) {
|
||||
//
|
||||
_state = state;
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
_key_map = key_map;
|
||||
_selector = selector;
|
||||
_result_key = result_key;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<TResultKey, TResult> state => _state;
|
||||
public IReadOnlyDictionary<TResultKey, TResult> added { get; }
|
||||
public IReadOnlyDictionary<TResultKey, TResult> removed { get; }
|
||||
|
||||
public PivotIncrementalUpdate<T, TKey, TResult, TResultKey> update(IIncrementalUpdate<T, TKey> table) {
|
||||
Dictionary<TResultKey, TResult> removed = new Dictionary<TResultKey, TResult>(), added = new Dictionary<TResultKey, TResult>();
|
||||
var state_clone = new Dictionary<TResultKey, TResult>(_state);
|
||||
foreach (var kv in table.removed) {
|
||||
var result = _selector(kv.Value);
|
||||
var rkey = _result_key(result);
|
||||
if (_key_map.removes_key(rkey, kv.Key))
|
||||
removed.Add(rkey, result);
|
||||
}
|
||||
foreach (var kv in table.added) {
|
||||
var result = _selector(kv.Value);
|
||||
var rkey = _result_key(result);
|
||||
if (_key_map.adds_key(rkey, kv.Key)) {
|
||||
if (!removed.Remove(rkey))
|
||||
added.Add(rkey, result);
|
||||
}
|
||||
}
|
||||
foreach (var k in removed.Keys)
|
||||
state_clone.Remove(k);
|
||||
foreach (var kv in added)
|
||||
state_clone.Add(kv.Key, kv.Value);
|
||||
return new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(state_clone, added, removed, _key_map, _selector, _result_key, _key);
|
||||
}
|
||||
}
|
||||
|
||||
class PivotTableUpdateTransform<T, TKey, TResult, TResultKey> : ITableUpdateTransform<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
|
||||
readonly Dictionary<TResultKey, TResult> _state;
|
||||
readonly SetMap<TResultKey, TKey> _key_map;
|
||||
|
||||
@@ -471,7 +471,7 @@ namespace mbts.ui.shell
|
||||
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new ObservablePropertySink<T>(this, name, default_value);
|
||||
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
_observable_properties.Add(source.ObserveOnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
return prop;
|
||||
}
|
||||
|
||||
@@ -526,8 +526,8 @@ namespace mbts.ui.shell
|
||||
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new SelectorProperty<T>(this, name, select_default, items_match, validation_steps);
|
||||
_observable_properties.Add(items.OnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(items.ObserveOnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(desired_item.ObserveOnDispatcher().Subscribe(prop));
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,220 +1,141 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.spark;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
using static mbts.config.Configuration;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public class Badge : Control {
|
||||
public const double LINE_SPACING = 1;
|
||||
// Storing the defaults here for readability
|
||||
// Warning: these properties are used to calculate the ladder's row_height. Please change the values below if you want to change properties that affect the layout.
|
||||
// ** - value is not the actual PropertyMetadata.DefaultValue
|
||||
public static readonly Thickness
|
||||
DefaultMargin = new Thickness(4, 2, 4, 2), // **
|
||||
DefaultPadding = new Thickness(1),
|
||||
DefaultBorderThickness = new Thickness(2);
|
||||
public static readonly double
|
||||
DefaultCornerRadius = 1,
|
||||
DefaultFontSize = 10,
|
||||
DefaultWidth = 63; // **
|
||||
public static readonly FontFamily DefaultFontFamily = new FontFamily("Consolas");
|
||||
|
||||
readonly GlyphContentRect _gcr = new GlyphContentRect();
|
||||
Brush _background, _foreground, _border_brush;
|
||||
public class Badge: Control {
|
||||
readonly static double _dpi = get<bts.config.System>().dpi_ratio;
|
||||
|
||||
Typeface _face;
|
||||
FormattedText _center_text;
|
||||
FormattedText _top_text;
|
||||
FormattedText _bottom_text;
|
||||
static Badge() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(typeof(Badge)));
|
||||
// MinWidth
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MinWidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.min_width = v,
|
||||
0);
|
||||
// Width
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
WidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.width = v,
|
||||
double.NaN);
|
||||
// Max Width
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MaxWidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.max_width = v,
|
||||
double.MaxValue);
|
||||
// Min Height
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MinHeightProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.min_height = v,
|
||||
0);
|
||||
// Height
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
HeightProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.height = v,
|
||||
double.NaN);
|
||||
// Max Height
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MaxHeightProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.max_height = v,
|
||||
double.MaxValue);
|
||||
// FontSize
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
FontSizeProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.font_size = v,
|
||||
DefaultFontSize);
|
||||
// Foreground
|
||||
WPFHelper.override_metadata<Brush, Badge>(
|
||||
ForegroundProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._foreground = v,
|
||||
Brushes.White);
|
||||
// Background
|
||||
WPFHelper.override_metadata<Brush, Badge>(
|
||||
BackgroundProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._background = v,
|
||||
Brushes.Transparent);
|
||||
// BorderThickness
|
||||
WPFHelper.override_metadata<Thickness, Badge>(
|
||||
BorderThicknessProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.border_thickness = v,
|
||||
DefaultBorderThickness);
|
||||
// BorderBrush
|
||||
WPFHelper.override_metadata<Brush, Badge>(
|
||||
BorderBrushProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._border_brush = v,
|
||||
Brushes.Black);
|
||||
// FontFamily
|
||||
WPFHelper.override_metadata<FontFamily, Badge>(
|
||||
FontFamilyProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.glyph_context = b._gcr.glyph_context.with(v),
|
||||
DefaultFontFamily);
|
||||
// HoriontalAlignment
|
||||
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
|
||||
HorizontalAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.horizontal_alignment = v,
|
||||
HorizontalAlignment.Stretch);
|
||||
// HoriontalContentAlignment
|
||||
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
|
||||
HorizontalContentAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.horizontal_content_alignment = v,
|
||||
HorizontalAlignment.Center);
|
||||
// VerticalAlignment
|
||||
WPFHelper.override_metadata<VerticalAlignment, Badge>(
|
||||
VerticalAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.vertical_alignment = v,
|
||||
VerticalAlignment.Center);
|
||||
// VerticalContentAlignment
|
||||
WPFHelper.override_metadata<VerticalAlignment, Badge>(
|
||||
VerticalContentAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.vertical_content_alignment = v,
|
||||
VerticalAlignment.Center);
|
||||
// Padding
|
||||
WPFHelper.override_metadata<Thickness, Badge>(
|
||||
PaddingProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(p, v) => p._gcr.padding = v,
|
||||
DefaultPadding);
|
||||
WidthProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(15.0));
|
||||
HeightProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0));
|
||||
FontSizeProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
|
||||
ForegroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.White, update_text));
|
||||
BackgroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
BorderThicknessProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new Thickness(1)));
|
||||
BorderBrushProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Black));
|
||||
FontFamilyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new FontFamily("Consolas"), FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
|
||||
|
||||
//TextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
|
||||
//TopTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
|
||||
//BottomTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
|
||||
}
|
||||
|
||||
public Badge() {
|
||||
_gcr.min_width = MinWidth;
|
||||
_gcr.width = Width;
|
||||
_gcr.max_width = MaxWidth;
|
||||
_gcr.min_height = MinHeight;
|
||||
_gcr.height = Height;
|
||||
_gcr.max_height = Height;
|
||||
_gcr.padding = Padding;
|
||||
_gcr.border_thickness = BorderThickness;
|
||||
_gcr.horizontal_alignment = HorizontalAlignment;
|
||||
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
|
||||
_gcr.vertical_alignment = VerticalAlignment;
|
||||
_gcr.vertical_content_alignment = VerticalContentAlignment;
|
||||
_background = Background;
|
||||
_foreground = Foreground;
|
||||
_border_brush = BorderBrush;
|
||||
_gcr.font_size = FontSize;
|
||||
_gcr.text_lines = TextLines;
|
||||
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
|
||||
public string Text {
|
||||
get { return (string)GetValue(TextProperty); }
|
||||
set { SetValue(TextProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register("Text", typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
|
||||
format_text((b,v) => b._center_text= v)));
|
||||
|
||||
|
||||
public string TopText {
|
||||
get { return (string)GetValue(TopTextProperty); }
|
||||
set { SetValue(TopTextProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty TopTextProperty =
|
||||
DependencyProperty.Register(nameof(TopText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
|
||||
format_text((b, v) => b._top_text = v)));
|
||||
|
||||
public string BottomText {
|
||||
get { return (string)GetValue(BottomTextProperty); }
|
||||
set { SetValue(BottomTextProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextLinesProperty =
|
||||
WPFHelper.create_dp<IReadOnlyList<string>, Badge>(
|
||||
nameof(TextLines),
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.text_lines = v,
|
||||
new string[0]);
|
||||
|
||||
public IReadOnlyList<string> TextLines {
|
||||
get => GetValue(TextLinesProperty) as IReadOnlyList<string>;
|
||||
set => SetValue(TextLinesProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty BottomTextProperty =
|
||||
DependencyProperty.Register(nameof(BottomText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
|
||||
format_text((b,v)=>b._bottom_text=v)));
|
||||
|
||||
public double CornerRadius {
|
||||
get { return (double)GetValue(CornerRadiusProperty); }
|
||||
set { SetValue(CornerRadiusProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CornerRadiusProperty =
|
||||
WPFHelper.create_dp<double, Badge>(nameof(CornerRadius), DefaultCornerRadius);
|
||||
DependencyProperty.Register("CornerRadius", typeof(double), typeof(Badge), new PropertyMetadata(3.0));
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize) => finalSize;
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize) {
|
||||
_gcr.available_size = availableSize;
|
||||
return _gcr.layout_size;
|
||||
static PropertyChangedCallback format_text(Action<Badge,FormattedText> f) {
|
||||
return (d, e) => {
|
||||
Badge b = (Badge)d;
|
||||
ensure_fontface(b);
|
||||
string text = e.NewValue as string;
|
||||
if (string.IsNullOrEmpty(text)) f(b, null);
|
||||
f(b, new FormattedText(text,
|
||||
System.Globalization.CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
b._face, b.FontSize, b.Foreground, _dpi));
|
||||
b.InvalidateVisual();
|
||||
};
|
||||
}
|
||||
static FormattedText format_text(Badge badge, DependencyProperty source) {
|
||||
FormattedText result = null;
|
||||
var value = badge.GetValue(source);
|
||||
format_text((b, v) => result = v)(badge, new DependencyPropertyChangedEventArgs(source, value, value));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void update_text(DependencyObject d, DependencyPropertyChangedEventArgs args) {
|
||||
// Force formatted text regeneration when font/color changes
|
||||
Badge badge = (Badge)d;
|
||||
badge._face = null;
|
||||
ensure_fontface(badge);
|
||||
badge._center_text = format_text(badge, TextProperty);
|
||||
badge._top_text = format_text(badge, TopTextProperty);
|
||||
badge._bottom_text = format_text(badge, BottomTextProperty);
|
||||
}
|
||||
|
||||
private static void ensure_fontface(Badge b) {
|
||||
b._face = b._face ?? new Typeface(b.FontFamily, b.FontStyle, b.FontWeight, b.FontStretch);
|
||||
}
|
||||
|
||||
//protected override Size MeasureOverride(Size availableSize) {
|
||||
// Double calc_height = 0.0;
|
||||
// Double calc_width = 0.0;
|
||||
// Size size = new Size(0, 0);
|
||||
|
||||
// Size center_size = new Size(_center_text.Width, _center_text.Height);
|
||||
// Size top_size = new Size(_top_text.Width, _top_text.Height);
|
||||
// Size bottom_size = new Size(_bottom_text.Width, _bottom_text.Height);
|
||||
|
||||
// calc_width = Math.Max(center_size.Width, top_size.Width);
|
||||
// calc_width = Math.Max(calc_width, bottom_size.Width);
|
||||
|
||||
// calc_height += center_size.Height;
|
||||
// calc_height += top_size.Height;
|
||||
// calc_height += bottom_size.Height;
|
||||
|
||||
// size.Width = calc_width;
|
||||
// size.Height = calc_height;
|
||||
|
||||
// return size;
|
||||
|
||||
//}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
base.OnRender(dc);
|
||||
double border_thickness = BorderThickness.Top;
|
||||
var pen = new Pen(BorderBrush, border_thickness);
|
||||
dc.DrawRoundedRectangle(Background, pen, new Rect(0, border_thickness, _gcr.layout_size.Width, _gcr.layout_size.Height), CornerRadius, CornerRadius);
|
||||
var gr = _gcr.glyph_run;
|
||||
if (gr != null)
|
||||
dc.DrawGlyphRun(_foreground, gr);
|
||||
dc.DrawRoundedRectangle(Background, pen, new Rect(0, 0 + border_thickness, ActualWidth, ActualHeight - 2 * border_thickness), CornerRadius, CornerRadius);
|
||||
if (_center_text != null)
|
||||
dc.DrawText(_center_text, new Point((ActualWidth-_center_text.Width)/2, (ActualHeight-_center_text.Height)/2));
|
||||
if (_top_text != null)
|
||||
dc.DrawText(_top_text, new Point(border_thickness, border_thickness));
|
||||
if (_bottom_text != null)
|
||||
dc.DrawText(_bottom_text, new Point(border_thickness, ActualHeight - _bottom_text.Height - border_thickness));
|
||||
}
|
||||
}
|
||||
|
||||
class BadgeTextLinesConverter : IMultiValueConverter {
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ObjectHelper.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
|
||||
return BadgeRender.format(side, quantity, filled, style);
|
||||
else return new string[0];
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ namespace mbts.ui.controls {
|
||||
Disposable.dispose(ref timer._trigger_sub);
|
||||
IObservable<object> newval = args.NewValue as IObservable<object>;
|
||||
if (newval != null) {
|
||||
timer._trigger_sub = newval.OnDispatcher().Subscribe(timer.start);
|
||||
timer._trigger_sub = newval.ObserveOnDispatcher().Subscribe(timer.start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using mbts.spark.filters;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -61,19 +60,13 @@ namespace mbts.ui.controls {
|
||||
WPFHelper.create_dp<ListSortDirection?, ColumnBase>(
|
||||
nameof(SortDirection), (o, v) => o.OnSortDirectionChanged(v));
|
||||
|
||||
public ListSortDirection? SortDirection {
|
||||
get => GetValue(SortDirectionProperty) as ListSortDirection?;
|
||||
set => SetValue(SortDirectionProperty, value);
|
||||
}
|
||||
|
||||
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
|
||||
public ListSortDirection? SortDirection { get => GetValue(SortDirectionProperty) as ListSortDirection?; set => SetValue(SortDirectionProperty, value); }
|
||||
|
||||
public string sort_member_path { get; set; }
|
||||
public double original_width { get; set; }
|
||||
public bool is_collapsed { get; set; }
|
||||
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
|
||||
sort_direction_changed?.Invoke(this, new_value);
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) { }
|
||||
}
|
||||
|
||||
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey> {
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark.filters {
|
||||
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer, comparer);
|
||||
|
||||
public static IDataGridColumnPersistentFilter<TRow, TState> Create<TRow, TValue, TState>(
|
||||
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
|
||||
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
|
||||
Func<TRow, TValue> get_row_val,
|
||||
Func<ISet<TValue>, TState> get_state,
|
||||
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
|
||||
@@ -115,14 +115,14 @@ namespace mbts.spark.filters {
|
||||
IComparer<TValue> comparer = null
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.OnDispatcher().Subscribe(new_items => {
|
||||
_items_sub = items.ObserveOnDispatcher().Subscribe(new_items => {
|
||||
_items_set.SetItems(new_items.Concat(_selected_items_set));
|
||||
_unsorted_items.sync_with(_items_set);
|
||||
});
|
||||
}
|
||||
|
||||
public DynamicItemsFilter(
|
||||
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
|
||||
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
|
||||
Func<TRow, TValue> get_row_val,
|
||||
Func<ISet<TValue>, TState> get_state,
|
||||
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
|
||||
@@ -131,7 +131,7 @@ namespace mbts.spark.filters {
|
||||
IComparer<TValue> comparer = null
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.OnDispatcher().Subscribe(update => {
|
||||
_items_sub = items.ObserveOnDispatcher().Subscribe(update => {
|
||||
foreach (var v in update.added)
|
||||
_items_set.Add(v);
|
||||
foreach (var v in update.removed.Except(_selected_items_set))
|
||||
@@ -182,7 +182,7 @@ namespace mbts.spark.filters {
|
||||
|
||||
protected override void _load_state_internal(TState state) {
|
||||
Disposable.dispose(ref _desired_items_sub);
|
||||
_desired_items_sub = _load_state(state).OnDispatcher().Subscribe(_on_desired_item);
|
||||
_desired_items_sub = _load_state(state).ObserveOnDispatcher().Subscribe(_on_desired_item);
|
||||
}
|
||||
|
||||
protected override void _apply() {
|
||||
|
||||
@@ -54,9 +54,8 @@ namespace mbts.ui {
|
||||
public static TimeTextTuple FromTicks(long ticks) =>
|
||||
FromDateTime(new DateTime(ticks));
|
||||
|
||||
public static TimeTextTuple FromDateTime(DateTime date_time) => FromDateTime(date_time, "t");
|
||||
public static TimeTextTuple FromDateTime(DateTime date_time, string time_format) =>
|
||||
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString(time_format));
|
||||
public static TimeTextTuple FromDateTime(DateTime date_time) =>
|
||||
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString("t"));
|
||||
|
||||
public TimeTextTuple(string month_text, string day_text, string year_text, string time_text) {
|
||||
_month_text = month_text;
|
||||
@@ -108,23 +107,14 @@ namespace mbts.ui {
|
||||
|
||||
|
||||
static DateTime? _parse(string month_text, string day_text, string year_text, string time_text) {
|
||||
TimeSpan? t = _parse_time(time_text);
|
||||
DateTime? t = _parse(time_text);
|
||||
if (t.HasValue) {
|
||||
DateTime? d = _parse($"{month_text}/{day_text}/{year_text}");
|
||||
if (d.HasValue) return d.Value.Date.Add(t.Value);
|
||||
if (d.HasValue) return d.Value.Date.AddDays(t.Value.TimeOfDay.TotalDays);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static TimeSpan? _parse_time(string text) {
|
||||
DateTime? dt = _parse(text);
|
||||
if (dt.HasValue) {
|
||||
return dt.Value.TimeOfDay;
|
||||
} else if (TimeSpan.TryParse(text, out TimeSpan result) && result < TimeSpan.FromDays(1)) {
|
||||
return result;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
static DateTime? _parse(string text) {
|
||||
if (!string.IsNullOrEmpty(text) && DateTime.TryParse(text, out DateTime result))
|
||||
return result;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding Mode, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
<Grid x:Name="PeriodRoot" Grid.Row="1" Margin="24,6,0,0" IsEnabled="False">
|
||||
<Grid x:Name="PeriodRoot" Grid.Row="1" Margin="0,6,0,0" IsEnabled="False">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="4"/>
|
||||
|
||||
@@ -73,9 +73,8 @@ namespace mbts.ui.controls {
|
||||
public enum TimeFilterMode {
|
||||
Anytime,
|
||||
Today,
|
||||
[Description("Period:")]
|
||||
Period,
|
||||
[Description("Since Open")]
|
||||
[Description("Glbx Open")]
|
||||
CMEOpen
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
<ResourceDictionary Source="/view/Controls/PriceEntryControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Time/TimeSpanControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/QuantityMeasureControl.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="uag:OrderDetailsPopupControl">
|
||||
@@ -156,186 +155,6 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.TimeBoxOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.TickOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Cross Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
|
||||
|
||||
<TextBlock Grid.Row="3" VerticalAlignment="Center">Cross Ticks</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding cross_ticks}"/>
|
||||
<TextBlock Grid.Row="5" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="5" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
|
||||
<CheckBox Grid.Row="7" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding detach_on_toward}">Detach on Toward</CheckBox>
|
||||
<TextBlock Grid.Row="9" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<TextBlock Grid.Row="11">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="11" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.TickOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.LeanOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Cross Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
|
||||
<TextBlock Grid.Row="3" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
|
||||
<TextBlock Grid.Row="5">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="5" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.LeanOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.JoinOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Join Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding join_threshold}"/>
|
||||
<CheckBox Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding enable_hedge}">Enable Hedge</CheckBox>
|
||||
<TextBlock Grid.Row="5" VerticalAlignment="Center" IsEnabled="{Binding enable_hedge}" >Hedge Ticks</TextBlock>
|
||||
<c:TickUpDownInt Grid.Row="5" Grid.Column="2" IsEnabled="{Binding enable_hedge}" Value="{Binding hedge_ticks}"/>
|
||||
<TextBlock Grid.Row="7">Hedge</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="7" Grid.Column="2"
|
||||
IsEnabled="{Binding enable_hedge}"
|
||||
IsOpen="{Binding hedge_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding hedge_order_types}"
|
||||
SelectedItem="{Binding hedge_order}"
|
||||
HasEditableProperties="{Binding hedge_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
<TextBlock Grid.Row="9" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<TextBlock Grid.Row="11">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="11" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.JoinOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.TopOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Top Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
|
||||
<CheckBox Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding enable_hedge}">Enable Hedge</CheckBox>
|
||||
<TextBlock Grid.Row="5" VerticalAlignment="Center" IsEnabled="{Binding enable_hedge}" >Hedge Ticks</TextBlock>
|
||||
<c:TickUpDownInt Grid.Row="5" Grid.Column="2" IsEnabled="{Binding enable_hedge}" Value="{Binding hedge_ticks}"/>
|
||||
<TextBlock Grid.Row="7">Hedge</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="7" Grid.Column="2"
|
||||
IsEnabled="{Binding enable_hedge}"
|
||||
IsOpen="{Binding hedge_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding hedge_order_types}"
|
||||
SelectedItem="{Binding hedge_order}"
|
||||
HasEditableProperties="{Binding hedge_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
<TextBlock Grid.Row="9" VerticalAlignment="Center">Watch Levels</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding watch_level_tick_offset}"/>
|
||||
<TextBlock Grid.Row="11">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="11" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.TopOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
@@ -361,30 +180,21 @@
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
|
||||
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.SpreaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:ExecutionStrategyEditor
|
||||
Margin="6"
|
||||
HorizontalAlignment="Stretch"
|
||||
DataContext="{Binding strategy_editor}"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
IsNew="False"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
PriceCalc="{Binding price_calc}"
|
||||
ParameterRows="{Binding rows}"
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="-4" RightItems="{Binding skirt_items}"/>
|
||||
</Grid>
|
||||
<usp:ExecutionStrategyEditor
|
||||
HorizontalAlignment="Stretch" Margin="6"
|
||||
DataContext="{Binding strategy_editor}"
|
||||
SyntheticName="{Binding name}"
|
||||
IsNew="{Binding is_new}"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
PriceCalc="{Binding price_calc}"
|
||||
ParameterRows="{Binding rows}"
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.SpreaderTemplate>
|
||||
</usp:OrderModificationTemplateSelector>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:Badge Background="{Binding Metadata, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeBrushConverter}}" BorderThickness="1" Width="15" Height="10" HorizontalAlignment="Left" />
|
||||
<c:Badge Background="{Binding Metadata, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeBrushConverter}}" Width="15" Height="10" HorizontalAlignment="Left" />
|
||||
<TextBlock Text="{Binding OrderType, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeDisplayTextConverter}}" Margin="20,0,0,0" HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -28,14 +28,7 @@
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TickDisplayTwoWayConverter}, Mode=TwoWay}"
|
||||
>
|
||||
<TextBox.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:TextBoxBehavior.SelectAllCommand}" Executed="{x:Static u:TextBoxBehavior.SelectAllCommandExecutedHandler}"/>
|
||||
</TextBox.CommandBindings>
|
||||
<TextBox.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static u:TextBoxBehavior.SelectAllCommand}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
/>
|
||||
</c:TickUpDown>
|
||||
</ControlTemplate>
|
||||
<Style TargetType="usp:PriceEntryControl">
|
||||
|
||||
@@ -41,36 +41,30 @@ namespace mbts.ui.spark {
|
||||
|
||||
#region ContractSpec
|
||||
public static readonly DependencyProperty ContractSpecProperty = DependencyProperty.Register("ContractSpec", typeof(ContractSpecificationImmutable), typeof(PriceEntryControl),
|
||||
new PropertyMetadata(SecurityDataHelper.Defaults.ContractSpecificationImmutablePennies, OnContractSpecChanged));
|
||||
new PropertyMetadata(SecurityDataHelper.Defaults.ContractSpecificationImmutable, OnContractSpecChanged));
|
||||
static void OnContractSpecChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((PriceEntryControl)d)._update_text_binding();
|
||||
public ContractSpecificationImmutable? ContractSpec { get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?; set => SetValue(ContractSpecProperty, value); }
|
||||
#endregion
|
||||
|
||||
TextBox _text;
|
||||
IDisposable _text_input_hook;
|
||||
|
||||
public PriceEntryControl() {
|
||||
TickUpCommand = new DelegateCommand(_tick_up);
|
||||
TickDownCommand = new DelegateCommand(_tick_down);
|
||||
}
|
||||
|
||||
IDisposable _keyboard_focus_sub;
|
||||
bool _has_keyboard_focus;
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
Disposable.dispose(ref _keyboard_focus_sub);
|
||||
_text = this.assert_template_child<TextBox>("_text");
|
||||
_keyboard_focus_sub = _text.SubscribeKeyboardFocus(val => _has_keyboard_focus = val);
|
||||
Disposable.dispose(ref _text_input_hook);
|
||||
_text_input_hook = _text.SubscribePreviewTextInput((s, e) => e.Handled = !RegexFilters.PriceInput.IsMatch(_text.PeekText(e)));
|
||||
_update_text_binding();
|
||||
}
|
||||
|
||||
void _tick_up() {
|
||||
if (_has_keyboard_focus) {
|
||||
_text.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
}
|
||||
double v = Value;
|
||||
if (double.IsNaN(v)) {
|
||||
SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
} else {
|
||||
if (double.IsNaN(v)) SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
else {
|
||||
var spec = ContractSpec;
|
||||
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickup(v));
|
||||
else SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
@@ -78,13 +72,9 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
|
||||
void _tick_down() {
|
||||
if (_has_keyboard_focus) {
|
||||
_text.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
}
|
||||
double v = Value;
|
||||
if (double.IsNaN(v)) {
|
||||
SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
} else {
|
||||
if (double.IsNaN(v)) SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
else {
|
||||
var spec = ContractSpec;
|
||||
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickdown(v));
|
||||
else SetCurrentValue(ValueProperty, TickDefaultValue);
|
||||
|
||||
@@ -10,11 +10,9 @@ namespace mbts.ui.controls {
|
||||
static SparkMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenu), new FrameworkPropertyMetadata(typeof(SparkMenu)));
|
||||
|
||||
public SparkMenu() {
|
||||
public SparkMenu() =>
|
||||
SetCurrentValue(FocusableProperty, false);
|
||||
}
|
||||
|
||||
|
||||
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
|
||||
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="c:TickFactorsPicker">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="c:TickFactorsPicker">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="4"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox
|
||||
MinWidth="48"
|
||||
IsEditable="True"
|
||||
DisplayMemberPath="value"
|
||||
SelectedValuePath="value"
|
||||
ItemsSource="{Binding TickUnitPresetsView, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SelectedValue="{Binding Controller.tick_unit, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Text="{Binding Controller.tick_unit, Converter={StaticResource DoubleTextConverter}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static c:TickFactorsPicker.TickUnitVisibleTextConverter}">
|
||||
<Binding Path="Controller.tick_base" RelativeSource="{RelativeSource AncestorType=c:TickFactorsPicker}"/>
|
||||
<Binding Path="value"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="1" Foreground="{u:BTSColor UI.input_disabled_text}" Text="{Binding value, Converter={x:Static c:TickFactorsPicker.TickUnitHiddenTextConverter}}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
Grid.Column="2"
|
||||
MinWidth="60"
|
||||
IsEditable="True"
|
||||
DisplayMemberPath="value"
|
||||
SelectedValuePath="value"
|
||||
ItemsSource="{Binding TickBasePresetsView, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SelectedValue="{Binding Controller.tick_base, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Text="{Binding Controller.tick_base, Converter={StaticResource DoubleTextConverter}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,157 +0,0 @@
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for TickPicker.xaml
|
||||
/// </summary>
|
||||
class TickFactorsPicker : Control {
|
||||
readonly ObservableCollection<double> _tick_unit_presets = new ObservableCollection<double>();
|
||||
|
||||
static TickFactorsPicker() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TickFactorsPicker), new FrameworkPropertyMetadata(typeof(TickFactorsPicker)));
|
||||
|
||||
public TickFactorsPicker() {
|
||||
Controller = new TickFactorsController(this);
|
||||
TickBasePresetsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickBaseVM.reduced_base_group)));
|
||||
((ListCollectionView)TickBasePresetsView).CustomSort = TickUtils.CompareUtils.base_comparer.wrap<double, TickBaseVM>(vm => vm.value).to_non_generic();
|
||||
TickUnitPresetsView = CollectionViewSource.GetDefaultView(new ObservableAutoWrapper<TickUnitVM, double>(_tick_unit_presets, v => new TickUnitVM(v)));
|
||||
TickUnitPresetsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickUnitVM.reduced_unit_group)));
|
||||
((ListCollectionView)TickUnitPresetsView).CustomSort = Comparer<double>.Default.wrap<double, TickUnitVM>(vm => vm.value).to_non_generic();
|
||||
}
|
||||
|
||||
public static IEnumerable<double> TickBasePresets =>
|
||||
new [] {
|
||||
8, 16, 32, 64, 100
|
||||
}.SelectMany(i => Enumerable.Range(0, 4).Select(log10 => i * Math.Pow(10, log10)));
|
||||
|
||||
public static IMultiValueConverter TickUnitVisibleTextConverter =>
|
||||
ValueConverter.create((double tick_base, double tick_unit) => {
|
||||
int display_digits = TickExtensions.display_digits(tick_base);
|
||||
return ((int)(tick_unit)).ToString(new string('0', display_digits));
|
||||
});
|
||||
|
||||
public static IValueConverter TickUnitHiddenTextConverter =>
|
||||
ValueConverter.create((double tick_unit) => new string(tick_unit.ToString().SkipWhile(c => c != '.').ToArray()));
|
||||
|
||||
public static readonly DependencyProperty SelectedTickFactorsProperty =
|
||||
WPFHelper.create_dp<TickFactors, TickFactorsPicker>(
|
||||
nameof(SelectedTickFactors),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.load_from(v),
|
||||
new TickFactors());
|
||||
|
||||
public TickFactors SelectedTickFactors {
|
||||
get => GetValue(SelectedTickFactorsProperty) as TickFactors;
|
||||
set => SetValue(SelectedTickFactorsProperty, value);
|
||||
}
|
||||
|
||||
public TickFactorsController Controller { get; }
|
||||
|
||||
public ICollectionView TickBasePresetsView { get; } = CollectionViewSource.GetDefaultView(TickBasePresets.Select(v => new TickBaseVM(v)).ToList());
|
||||
public ICollectionView TickUnitPresetsView { get; }
|
||||
|
||||
static IEnumerable<double> _get_tick_unit_presets(double tick_base) {
|
||||
double reduced_base = MathUtils.factor_out(tick_base, 10, out int base_log10);
|
||||
return reduced_base == 1 ?
|
||||
_get_dbased_tick_unit_presets(base_log10) :
|
||||
_get_2based_tick_unit_presets((int)Math.Log(reduced_base, 2), base_log10);
|
||||
}
|
||||
|
||||
static IEnumerable<double> _get_2based_tick_unit_presets(int log2, int log10) =>
|
||||
(log10 == 0 ? new[] { 1.0 } : _get_dbased_tick_unit_presets(log10)).Concat(
|
||||
Enumerable.Range(1, log2 - 1).SelectMany(pow2 => {
|
||||
double root = Math.Pow(2, pow2);
|
||||
return Enumerable.Range(0, log10 + 1).Select(pow10 => root * Math.Pow(10, pow10));
|
||||
}));
|
||||
|
||||
static IEnumerable<double> _get_dbased_tick_unit_presets(int log10) =>
|
||||
Enumerable.Range(0, log10).SelectMany(_coins);
|
||||
|
||||
static IEnumerable<double> _coins(int pow10) {
|
||||
double d = Math.Pow(10, pow10);
|
||||
yield return d;
|
||||
yield return 1.25 * d;
|
||||
yield return 2.5 * d;
|
||||
yield return 5.0 * d;
|
||||
}
|
||||
|
||||
public class TickFactorsController : ViewModelBase, IValueFactoryVM<TickFactors> {
|
||||
readonly TickFactorsPicker _view;
|
||||
readonly Property<double> _tick_base, _tick_unit;
|
||||
|
||||
public TickFactorsController(TickFactorsPicker view) {
|
||||
var init = (_view = view).SelectedTickFactors;
|
||||
_tick_base = backing(nameof(tick_base), init.tick_base);
|
||||
_tick_unit = backing(nameof(tick_unit), init.tick_unit);
|
||||
}
|
||||
|
||||
public double tick_base {
|
||||
get => _tick_base.Value;
|
||||
set {
|
||||
_tick_base.set(value);
|
||||
WPFHelper.set_current_if_changed(_view, SelectedTickFactorsProperty, new TickFactors(value, tick_unit));
|
||||
_view._tick_unit_presets.sync_with(_get_tick_unit_presets(value));
|
||||
}
|
||||
}
|
||||
public double tick_unit {
|
||||
get => _tick_unit.Value;
|
||||
set {
|
||||
_tick_unit.set(value);
|
||||
WPFHelper.set_current_if_changed(_view, SelectedTickFactorsProperty, new TickFactors(tick_base, value));
|
||||
}
|
||||
}
|
||||
|
||||
public TickFactors create() => new TickFactors(tick_base, tick_unit);
|
||||
public void load_from(TickFactors value) {
|
||||
if (value != null) {
|
||||
_tick_base.set(value.tick_base);
|
||||
_tick_unit.set(value.tick_unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TickBaseVM : IEquatable<TickBaseVM> {
|
||||
public TickBaseVM(double value) =>
|
||||
this.value = value;
|
||||
|
||||
public double value { get; }
|
||||
public double reduced_base_group => MathUtils.factor_out(value, 10);
|
||||
|
||||
public bool Equals(TickBaseVM other) => other != null && value == other.value;
|
||||
public override bool Equals(object obj) => Equals(obj as TickBaseVM);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
|
||||
public class TickUnitVM : IEquatable<TickUnitVM> {
|
||||
public TickUnitVM(double value) =>
|
||||
this.value = value;
|
||||
|
||||
public double value { get; }
|
||||
public double reduced_unit_group {
|
||||
get {
|
||||
if (!MathUtils.is_int(value))
|
||||
return 1;
|
||||
else return MathUtils.factor_out(value, 10, 5);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(TickUnitVM other) => other != null && value == other.value;
|
||||
public override bool Equals(object obj) => Equals(obj as TickUnitVM);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,42 +23,38 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e) {
|
||||
if (EnableArrowKeyTick) {
|
||||
switch (Keyboard.Modifiers) {
|
||||
case ModifierKeys.None: {
|
||||
switch (e.Key) {
|
||||
case Key.Up:
|
||||
TickUpCommand.TryExecuteIfCan(TickUpParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Down:
|
||||
TickDownCommand.TryExecuteIfCan(TickDownParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
switch (Keyboard.Modifiers) {
|
||||
case ModifierKeys.None: {
|
||||
switch (e.Key) {
|
||||
case Key.Up:
|
||||
TickUpCommand.TryExecuteIfCan(TickUpParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Down:
|
||||
TickDownCommand.TryExecuteIfCan(TickDownParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ModifierKeys.Alt: {
|
||||
// see: https://stackoverflow.com/questions/3099472/previewkeydown-is-not-seeing-alt-modifiers
|
||||
switch (e.SystemKey) {
|
||||
case Key.Up:
|
||||
TickUpCommand.TryExecuteIfCan(TickUpAltParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Down:
|
||||
TickDownCommand.TryExecuteIfCan(TickDownAltParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ModifierKeys.Alt: {
|
||||
// see: https://stackoverflow.com/questions/3099472/previewkeydown-is-not-seeing-alt-modifiers
|
||||
switch (e.SystemKey) {
|
||||
case Key.Up:
|
||||
TickUpCommand.TryExecuteIfCan(TickUpAltParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Down:
|
||||
TickDownCommand.TryExecuteIfCan(TickDownAltParameter);
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
base.OnPreviewKeyDown(e);
|
||||
}
|
||||
|
||||
public bool EnableArrowKeyTick { get; set; } = true;
|
||||
|
||||
#region TickCommand
|
||||
public ICommand TickCommand {
|
||||
get { return (ICommand)GetValue(TickCommandProperty); }
|
||||
|
||||
@@ -31,14 +31,8 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:TickUpDown x:Name="PART_time_tick" Grid.Column="0" MinWidth="85" HorizontalAlignment="Stretch" EnableArrowKeyTick="False">
|
||||
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int 1}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int -1}"/>
|
||||
<u:CustomInputBinding Gesture="{x:Static u:InputGestures.LeftTripleClick}" Command="{x:Static u:TextBoxBehavior.SelectAllCommand}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
<c:TickUpDown x:Name="PART_time_tick" Grid.Column="0" MinWidth="85" HorizontalAlignment="Stretch">
|
||||
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
|
||||
</c:TickUpDown>
|
||||
<c:TickUpDown x:Name="PART_month_tick" Grid.Column="2" Width="50">
|
||||
<TextBox x:Name="PART_month_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
|
||||
|
||||
@@ -3,7 +3,6 @@ using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
@@ -15,9 +14,7 @@ namespace mbts.ui.controls {
|
||||
/// Interaction logic for DateTimePicker.xaml
|
||||
/// </summary>
|
||||
public class DateTimePicker : Control {
|
||||
public static readonly RoutedCommand TickTimeComponentCommand = new RoutedCommand();
|
||||
|
||||
DTPController _controller;
|
||||
TimeTupleVM _vm;
|
||||
bool _suspend_notify_vm;
|
||||
|
||||
static DateTimePicker() =>
|
||||
@@ -27,8 +24,8 @@ namespace mbts.ui.controls {
|
||||
new FrameworkPropertyMetadata(default(TimeTextTuple), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnTimeTupleChanged));
|
||||
static void OnTimeTupleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var dtp = (DateTimePicker)d;
|
||||
if (!dtp._suspend_notify_vm && dtp._controller != null)
|
||||
dtp._controller.result = (TimeTextTuple)e.NewValue;
|
||||
if (!dtp._suspend_notify_vm && dtp._vm != null)
|
||||
dtp._vm.result = (TimeTextTuple)e.NewValue;
|
||||
}
|
||||
public TimeTextTuple TimeTuple { get => (TimeTextTuple)GetValue(TimeTupleProperty); set => SetValue(TimeTupleProperty, value); }
|
||||
|
||||
@@ -44,74 +41,59 @@ namespace mbts.ui.controls {
|
||||
var time = this.assert_template_child<TickUpDown>("PART_time_tick");
|
||||
var time_text = this.assert_template_child<TextBox>("PART_time_text");
|
||||
|
||||
if (_controller != null) _controller.PropertyChanged -= _vm_PropertyChanged;
|
||||
_controller = new DTPController(time_text);
|
||||
_controller.result = TimeTuple;
|
||||
_controller.PropertyChanged += _vm_PropertyChanged;
|
||||
time.CommandBindings.Add(new CommandBinding(TickTimeComponentCommand, (s, e) => {
|
||||
_controller.tick_time_component((int)e.Parameter);
|
||||
}));
|
||||
time.CommandBindings.Add(new CommandBinding(TextBoxBehavior.SelectAllCommand, TextBoxBehavior.SelectAllCommandExecutedHandler));
|
||||
if (_vm != null) _vm.PropertyChanged -= _vm_PropertyChanged;
|
||||
_vm = new TimeTupleVM();
|
||||
_vm.result = TimeTuple;
|
||||
_vm.PropertyChanged += _vm_PropertyChanged;
|
||||
|
||||
const int delay = 500;
|
||||
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _controller });
|
||||
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _vm });
|
||||
time.TickUpParameter = 1;
|
||||
time.TickDownParameter = -1;
|
||||
time.TickUpAltParameter = 60;
|
||||
time.TickDownAltParameter = -60;
|
||||
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _controller, Delay = delay, ValidatesOnExceptions = true });
|
||||
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) => {
|
||||
string preview = time_text.PeekText(e);
|
||||
if (RegexFilters.Time.StandardTimeOptionalSeconds.IsMatch(preview)) {
|
||||
_controller.use_military_time = false;
|
||||
} else if (RegexFilters.Time.MilitaryTimeOptionalSeconds.IsMatch(preview)) {
|
||||
_controller.use_military_time = true;
|
||||
} else {
|
||||
e.Handled = true;
|
||||
}
|
||||
}));
|
||||
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _vm, Delay = delay, ValidatesOnExceptions = true });
|
||||
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !MatchAny(time_text.PeekText(e), RegexFilters.Time.StandardTime, RegexFilters.Time.MilitaryTime)
|
||||
));
|
||||
|
||||
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _controller });
|
||||
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _vm });
|
||||
month.TickUpParameter = 1;
|
||||
month.TickDownParameter = -1;
|
||||
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _controller, Delay = delay });
|
||||
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _vm, Delay = delay });
|
||||
_hooks.Add(month_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.MonthAbbrev.IsMatch(month_text.PeekText(e))
|
||||
));
|
||||
|
||||
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _controller });
|
||||
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _vm });
|
||||
day.TickUpParameter = 1;
|
||||
day.TickDownParameter = -1;
|
||||
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _controller, Delay = delay });
|
||||
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _vm, Delay = delay });
|
||||
_hooks.Add(day_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.DaysInMonth.IsMatch(day_text.PeekText(e))
|
||||
));
|
||||
|
||||
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _controller });
|
||||
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _vm });
|
||||
year.TickUpParameter = 1;
|
||||
year.TickDownParameter = -1;
|
||||
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _controller, Delay = delay });
|
||||
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _vm, Delay = delay });
|
||||
_hooks.Add(year_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.Year.IsMatch(year_text.PeekText(e))
|
||||
));
|
||||
}
|
||||
|
||||
void _vm_PropertyChanged(object sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(DTPController.result)) {
|
||||
if (e.PropertyName == nameof(TimeTupleVM.result)) {
|
||||
_suspend_notify_vm = true;
|
||||
TimeTuple = _controller.result;
|
||||
TimeTuple = _vm.result;
|
||||
_suspend_notify_vm = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This is not exactly a view-model, bc it accesses the time TextBox directly so it can get/set the CaretIndex,
|
||||
// which is used to determine which time-componenet - hh:mm:ss AM/PM - should be incremented/decremented via the up/down arrow-keys.
|
||||
class DTPController : ViewModelBase {
|
||||
readonly TextBox _time_text_box;
|
||||
private class TimeTupleVM : ViewModelBase {
|
||||
TimeTextTuple _result;
|
||||
|
||||
public DTPController(TextBox time_text_box) {
|
||||
_time_text_box = time_text_box;
|
||||
public TimeTupleVM() {
|
||||
tick_time = new DelegateCommand<int>(_tick_time);
|
||||
tick_month = new DelegateCommand<int>(_tick_month);
|
||||
tick_day = new DelegateCommand<int>(_tick_day);
|
||||
@@ -153,52 +135,12 @@ namespace mbts.ui.controls {
|
||||
RaisePropertyChanged(nameof(month_text), nameof(day_text), nameof(year_text), nameof(time_text));
|
||||
}
|
||||
}
|
||||
|
||||
public bool use_military_time { get; set; }
|
||||
public ICommand tick_time { get; }
|
||||
public ICommand tick_month { get; }
|
||||
public ICommand tick_day { get; }
|
||||
public ICommand tick_year { get; }
|
||||
string _time_format => use_military_time ? StringFormats.Time.MilitaryWithSeconds : StringFormats.Time.StandardWithSeconds;
|
||||
|
||||
public void tick_time_component(int increment) {
|
||||
_ensure_time_text_up_to_date();
|
||||
var r = _result.result;
|
||||
if (r.HasValue) {
|
||||
int index = _time_text_box.CaretIndex;
|
||||
using (detect_property_changes()) {
|
||||
DateTime dt;
|
||||
if (index <= 0) {
|
||||
dt = r.Value.AddHours(increment);
|
||||
} else {
|
||||
int segment = _time_text_box.Text.Take(index).Count(c => c == ':');
|
||||
switch (segment) {
|
||||
case 0: dt = r.Value.AddHours(increment); break;
|
||||
case 1: dt = r.Value.AddMinutes(increment); break;
|
||||
case 2:
|
||||
if (_time_text_box.Text.Take(index).Contains(' ')) {
|
||||
// toggle AM/PM
|
||||
dt = r.Value.AddHours(r.Value.Hour < 12 ? 12 : -12);
|
||||
} else {
|
||||
dt = r.Value.AddSeconds(increment);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dt = r.Value.AddSeconds(increment);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_result = TimeTextTuple.FromDateTime(dt, _time_format);
|
||||
}
|
||||
_time_text_box.CaretIndex = index;
|
||||
} else {
|
||||
using (detect_property_changes())
|
||||
_force_valid_inputs();
|
||||
}
|
||||
}
|
||||
|
||||
void _tick_time(int increment) {
|
||||
_ensure_time_text_up_to_date();
|
||||
using (detect_property_changes()) {
|
||||
var r = _result.result;
|
||||
if (r.HasValue) {
|
||||
@@ -245,11 +187,6 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _ensure_time_text_up_to_date() {
|
||||
if (_time_text_box.IsKeyboardFocused)
|
||||
_time_text_box.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
}
|
||||
|
||||
void _force_valid_inputs() {
|
||||
var dt = DateTime.Today.Date;
|
||||
|
||||
@@ -2,15 +2,8 @@
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
static class ValueConverter {
|
||||
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert) =>
|
||||
new AnonymousValueConverter<TIn, TOut>(convert);
|
||||
|
||||
public static IMultiValueConverter create<TIn1, TIn2, TOut>(Func<TIn1, TIn2, TOut> convert) =>
|
||||
new AnonymousMultiValueConverter<TIn1, TIn2, TOut>(convert);
|
||||
}
|
||||
|
||||
namespace mbts.ui.converters
|
||||
{
|
||||
public class AnonymousValueConverter : IValueConverter {
|
||||
public AnonymousValueConverter(Func<object, object> convert) : this(convert, null) { }
|
||||
public AnonymousValueConverter(Func<object, object> convert, Func<object, object> convert_back) {
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace mbts.ui.converters
|
||||
internal class EnumCharacterCasingConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
|
||||
if (value is null) return string.Empty;
|
||||
string s = value is Enum ? EnumHelper.get_display_name((Enum)value) : value.ToString();
|
||||
|
||||
CharacterCasing casing;
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace mbts.ui.converters
|
||||
Minimum = Double.NaN;
|
||||
Maximum = Double.NaN;
|
||||
Multiplier = Double.NaN;
|
||||
Format = FORMAT_WITH_COMMAS;
|
||||
HideNaN = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.converters {
|
||||
class TickFactorsTextConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is TickFactors tf) {
|
||||
int n_digits = tf.display_digits();
|
||||
int n_digits = TickExtensions.n_display_digits(tf.tick_base);
|
||||
if (n_digits > 1) {
|
||||
string n_digits_format = new string('0', n_digits);
|
||||
string format = "{0:" + n_digits_format + "}/{1}";
|
||||
|
||||
@@ -61,16 +61,16 @@ namespace mbts.ui
|
||||
_source_window = window as FloatingWindow;
|
||||
if (_source != null) {
|
||||
_mouse_start = e.GetPosition(window);
|
||||
if (!is_clickable_descendant((DependencyObject)e.OriginalSource)) {
|
||||
if (!is_button_descendant((DependencyObject)e.OriginalSource)) {
|
||||
Mouse.Capture(_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_clickable_descendant(DependencyObject target) {
|
||||
static bool is_button_descendant(DependencyObject target) {
|
||||
while (target != null && !(target is TabDocument)) {
|
||||
target = VisualTreeHelper.GetParent(target);
|
||||
if (target is Button || target is TextBox) return true;
|
||||
if (target is Button) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,38 @@ namespace mbts.ui
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this DependencyObject target, string event_name) {
|
||||
public static async Task FromEvent(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
Action result = () => { tcs.TrySetResult(null); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T> FromEvent<T>(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
Action<T> result = (arg) => { tcs.TrySetResult(arg); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
return tcs.Task.Result;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<Tuple<T1,T2>>();
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace mbts.ui
|
||||
|
||||
private void _subscribe() {
|
||||
Disposable.dispose(ref _sub);
|
||||
_sub = _source.OnDispatcher().Subscribe(_on_data);
|
||||
_sub = _source.ObserveOnDispatcher().Subscribe(_on_data);
|
||||
}
|
||||
private void _on_data(T data) { Target.SetValue(_target_property, _last_value = data); }
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui {
|
||||
class CustomInputBinding : InputBinding { }
|
||||
|
||||
static class InputGestures {
|
||||
public static readonly InputGesture LeftTripleClick = new LeftTripleClickGesture();
|
||||
|
||||
class LeftTripleClickGesture : InputGesture {
|
||||
public override bool Matches(object targetElement, InputEventArgs inputEventArgs) =>
|
||||
inputEventArgs is MouseButtonEventArgs mouse_args &&
|
||||
mouse_args.ChangedButton == MouseButton.Left &&
|
||||
mouse_args.ClickCount == 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui {
|
||||
static class TextBoxBehavior {
|
||||
#region RoutedCommands/Handlers
|
||||
public static readonly RoutedCommand
|
||||
ReleaseCaptureCommand = new RoutedCommand(),
|
||||
SelectAllCommand = new RoutedCommand();
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler SelectAllCommandExecutedHandler =
|
||||
new ExecutedRoutedEventHandler((s, e) => (e.OriginalSource as TextBox ?? e.Source as TextBox ?? s as TextBox)?.SelectAll());
|
||||
#endregion
|
||||
public static readonly RoutedCommand ReleaseCaptureCommand = new RoutedCommand();
|
||||
|
||||
#region SelectAllOnFocus
|
||||
|
||||
@@ -175,7 +168,7 @@ namespace mbts.ui {
|
||||
public static void SetInputFilter(DependencyObject d, TextBoxInputFilter value) => d.SetValue(InputFilterProperty, value);
|
||||
#endregion
|
||||
|
||||
#region IncrementDecrementOnMouseClick
|
||||
#region
|
||||
public static readonly DependencyProperty IncrementDecrementOnMouseClickProperty = DependencyProperty.RegisterAttached(
|
||||
"IncrementDecrementOnMouseClick", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, IncrementDecrementOnMouseClickPropertyChanged)
|
||||
);
|
||||
|
||||
@@ -2,28 +2,11 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui {
|
||||
static class TextElementHelper {
|
||||
public static IDisposable SubscribeKeyboardFocus(this TextBox text_box, Action<bool> callback) {
|
||||
text_box.GotKeyboardFocus += on_got_keyboard_focus;
|
||||
text_box.LostKeyboardFocus += on_lost_keyboard_focus;
|
||||
return Disposable.Create(() => {
|
||||
text_box.GotKeyboardFocus -= on_got_keyboard_focus;
|
||||
text_box.LostKeyboardFocus -= on_lost_keyboard_focus;
|
||||
});
|
||||
void on_got_keyboard_focus(object sender, KeyboardFocusChangedEventArgs e) {
|
||||
if (e.NewFocus == text_box)
|
||||
callback(true);
|
||||
|
||||
}
|
||||
void on_lost_keyboard_focus(object sender, KeyboardFocusChangedEventArgs e) {
|
||||
if (e.OldFocus == text_box)
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
public class TextElementHelper
|
||||
{
|
||||
public static void GetTargetProperty(DependencyObject d, out FrameworkElement text_element, out DependencyProperty text_property) {
|
||||
text_element = (FrameworkElement)d;
|
||||
if (text_element is TextBox) text_property = TextBox.TextProperty;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Window}">
|
||||
<Border BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Loaded="style_loaded" x:Name="window_border" u:WindowStyle.IsNonClientArea="true">
|
||||
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Loaded="style_loaded" x:Name="window_border" u:WindowStyle.IsNonClientArea="true">
|
||||
<Grid Margin="{TemplateBinding Margin}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="20" />
|
||||
@@ -134,8 +134,8 @@
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource self}}" Value="Normal">
|
||||
<Setter Property="Padding" Value="0,0,2,2" />
|
||||
<Setter Property="BorderThickness" Value="7,1,1,1" TargetName="window_border" />
|
||||
<Setter Property="Padding" Value="0,0,0,0" />
|
||||
<Setter Property="BorderThickness" Value="7,1,2.66,2.66" TargetName="window_border" />
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
Loaded 100 of 328 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user