Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0fdf74a73 | ||
|
|
ebedd17129 | ||
|
|
0f8ed5dacb | ||
|
|
8553229119 | ||
|
|
4903c12533 | ||
|
|
88f23530c6 | ||
|
|
14f4452599 | ||
|
|
22002c43cf | ||
|
|
7551e23877 | ||
|
|
22c92c3e17 | ||
|
|
613823e7c5 | ||
|
|
f0a9a2415d | ||
|
|
708e63e36e | ||
|
|
76ff36e514 | ||
|
|
740c14c261 | ||
|
|
53990297e8 | ||
|
|
9570be8bfa | ||
|
|
0b15570d00 | ||
|
|
33a71f5f47 | ||
|
|
2940601f4b | ||
|
|
1f7939f49a | ||
|
|
48e273a607 | ||
|
|
e35634020d | ||
|
|
1ede053f59 | ||
|
|
93824786c7 | ||
|
|
25b8c0a728 | ||
|
|
0e4fe1b47e | ||
|
|
cd744963ff | ||
|
|
fb9d877938 | ||
|
|
087b001510 | ||
|
|
02a7c0ddca | ||
|
|
6afa1d581d | ||
|
|
5e04abe573 | ||
|
|
3422a44c87 | ||
|
|
d5fdcc6796 | ||
|
|
f12217d76c | ||
|
|
d85a914046 | ||
|
|
4fdc890cb5 | ||
|
|
3ed0bd9052 | ||
|
|
dbdc915ff2 | ||
|
|
10476c6662 | ||
|
|
5fbc1397b5 | ||
|
|
05e09cb977 | ||
|
|
38bd2ef321 | ||
|
|
dab4f5acbb | ||
|
|
a20c53b23d | ||
|
|
8206f457b5 | ||
|
|
d4e6d6293d | ||
|
|
d1a2197ba4 | ||
|
|
369fb55e90 | ||
|
|
37dacff74d | ||
|
|
d297b703c7 | ||
|
|
59c00203a9 | ||
|
|
33f533af9b | ||
|
|
085d6402f4 | ||
|
|
f4395ef833 | ||
|
|
e7a9412124 | ||
|
|
da4ee38abd | ||
|
|
706e5bc73b | ||
|
|
af39fac368 | ||
|
|
4f997505f3 | ||
|
|
fcbb66361d | ||
|
|
fa16321691 | ||
|
|
ceb3542e15 | ||
|
|
32e55d07ee | ||
|
|
e079acf91f | ||
|
|
fecedb203a | ||
|
|
6ac05a47e1 | ||
|
|
36ed80dc87 | ||
|
|
6cf395199e | ||
|
|
1707c3268f | ||
|
|
f5bbc4245f | ||
|
|
96fc54ff0f | ||
|
|
25b69e4f7d | ||
|
|
d871ffca6d | ||
|
|
213567bcdb | ||
|
|
b7e5c3a374 | ||
|
|
ae4a8bbf46 | ||
|
|
850386c1d4 | ||
|
|
2d4e94a636 | ||
|
|
9b73ad2486 | ||
|
|
e447096733 | ||
|
|
cd68b95a3e | ||
|
|
205eebb7da | ||
|
|
ef4dc1fc65 | ||
|
|
ebad5b389f | ||
|
|
160c49514a | ||
|
|
4f7fcc5634 | ||
|
|
500e5b5fe4 | ||
|
|
9d924fa4ba | ||
|
|
092e63e300 | ||
|
|
4654cd4739 | ||
|
|
f2f06d76de | ||
|
|
6bc293a58e | ||
|
|
fdb462eda4 | ||
|
|
cc579034b2 | ||
|
|
4b76e91757 | ||
|
|
8d34279ab0 | ||
|
|
7973b927a2 | ||
|
|
6997aed9bd | ||
|
|
b5d7d98098 | ||
|
|
8f22a326ab | ||
|
|
b2c3488c5e | ||
|
|
23f10eac70 | ||
|
|
cb31269a70 | ||
|
|
5b75b8718a | ||
|
|
5a256d4634 | ||
|
|
db2de3e8c4 | ||
|
|
b9dcad9549 | ||
|
|
ce104479e1 | ||
|
|
2d56eedf3e | ||
|
|
049381908c | ||
|
|
0067e52155 | ||
|
|
8ed6d297a9 | ||
|
|
474401cddc | ||
|
|
a06df5099a | ||
|
|
8ddd5bdbb5 | ||
|
|
5cbe00beab | ||
|
|
3ebcf9a270 | ||
|
|
40cd826260 | ||
|
|
8bd63f11b1 | ||
|
|
2198a8d6ed | ||
|
|
8023dc7aa8 | ||
|
|
e2b0da8103 | ||
|
|
aea233d537 | ||
|
|
5756c090f3 | ||
|
|
5ab0acb0ef | ||
|
|
3950e5b634 | ||
|
|
7ce8f1ed97 | ||
|
|
5d53492ab5 | ||
|
|
90fd5bd382 | ||
|
|
beff060a4f | ||
|
|
bc3418734f | ||
|
|
ab255c8777 | ||
|
|
cc2bde1064 | ||
|
|
94d66a8e99 | ||
|
|
92f08b89cc | ||
|
|
02769479ea | ||
|
|
4ada322c56 | ||
|
|
7de7f6e496 | ||
|
|
0700e9e80d | ||
|
|
ccca85d1c5 | ||
|
|
60e933fca9 | ||
|
|
e2aa6034ab | ||
|
|
8b4c64e8f4 | ||
|
|
b5ea399e7e | ||
|
|
62baa6aa31 | ||
|
|
87433aa7c3 | ||
|
|
3252cab4bb | ||
|
|
fbe9b570a3 | ||
|
|
1bcb209961 | ||
|
|
b0726ad3ea | ||
|
|
e0ba27a0a9 | ||
|
|
345e45c5bf | ||
|
|
490add84d9 | ||
|
|
94fc2c4248 | ||
|
|
8a7e2db4ac | ||
|
|
71c576bbc0 | ||
|
|
2e4dff4d91 | ||
|
|
73336df814 | ||
|
|
633edb43eb | ||
|
|
f4add8d464 | ||
|
|
894335d019 | ||
|
|
0fc977f5d3 | ||
|
|
3d70d35b99 | ||
|
|
66307edf91 | ||
|
|
e3e525819e | ||
|
|
ece25dd946 | ||
|
|
24f824fd81 | ||
|
|
7d992c84ab | ||
|
|
1cce474485 | ||
|
|
7186c0349b | ||
|
|
3d829da004 | ||
|
|
f20fd2136e | ||
|
|
30ce85b70f | ||
|
|
fc7a04a805 | ||
|
|
6d96c0e2a0 | ||
|
|
ae913962b3 | ||
|
|
b52f6fdc24 | ||
|
|
29d14404e6 | ||
|
|
d13f1b04ed | ||
|
|
8d234d9b8b | ||
|
|
d261d6fc31 | ||
|
|
343bf25aaf | ||
|
|
0f93757506 | ||
|
|
60b7966f2f | ||
|
|
6cad454f9e | ||
|
|
b3f6649e7f | ||
|
|
1059351334 | ||
|
|
3263cc840a | ||
|
|
8d0e93a311 | ||
|
|
179ee000a3 | ||
|
|
43129427f6 | ||
|
|
f49d73640e | ||
|
|
7afe1e103d | ||
|
|
c1b701ff2b | ||
|
|
06ca15ccac | ||
|
|
027efa9d2a | ||
|
|
026695d0d1 | ||
|
|
50775b3925 | ||
|
|
c695b43647 | ||
|
|
56ace9514a | ||
|
|
7c6a26ebd9 | ||
|
|
c6c1659953 | ||
|
|
dc696e9468 | ||
|
|
a6189d910c | ||
|
|
049cc69a4f | ||
|
|
b2766c283d | ||
|
|
0b14b048e4 | ||
|
|
ffc03a4055 | ||
|
|
992eb7b104 | ||
|
|
314eca0e89 | ||
|
|
626ec79af4 | ||
|
|
953272b231 | ||
|
|
4c185093bb | ||
|
|
6b5b4ea2b1 | ||
|
|
6f7c6db918 | ||
|
|
a1ead3d1c1 | ||
|
|
1fc35ee3cd | ||
|
|
af576bbee7 | ||
|
|
7b9ed4326a | ||
|
|
615e585c82 | ||
|
|
a014dcc984 | ||
|
|
42645f1246 | ||
|
|
1c5507a3f4 | ||
|
|
a92dd5a841 | ||
|
|
91a6c946f8 | ||
|
|
cfbd7855a5 | ||
|
|
00e9862844 | ||
|
|
4d413fb92c | ||
|
|
6e07a0c109 | ||
|
|
e936b80dc6 | ||
|
|
4046f82e06 | ||
|
|
699a6526f5 | ||
|
|
12a4c67e4d | ||
|
|
e9562b55f3 | ||
|
|
43735e1dab | ||
|
|
930723dd0e | ||
|
|
3e4ee9d6b6 | ||
|
|
c771aea311 | ||
|
|
7b35467d59 | ||
|
|
192354946d | ||
|
|
1eacb7f0f0 | ||
|
|
d0695fd5d7 | ||
|
|
ad8377d13f | ||
|
|
60ab67213f | ||
|
|
4055a06601 | ||
|
|
883582630d | ||
|
|
e7728203cf | ||
|
|
dd8586133f | ||
|
|
e47af3d7bd | ||
|
|
f4aa5cfa8c | ||
|
|
bc3f2db0f9 | ||
|
|
ed08cb11d7 | ||
|
|
32eeeee94f | ||
|
|
c3c427742d | ||
|
|
83c432d8e2 | ||
|
|
6a4930767b | ||
|
|
685e69adcc | ||
|
|
fe2b167fd5 | ||
|
|
04fbcc49b1 | ||
|
|
32229e3236 | ||
|
|
e9f61dc3c0 | ||
|
|
f13949ed0f | ||
|
|
f329470165 | ||
|
|
f2b7e692f8 | ||
|
|
2cff906c5f | ||
|
|
f711715350 | ||
|
|
e76a72e3e6 | ||
|
|
d7d319f1e3 | ||
|
|
cddadcb278 | ||
|
|
419833d398 | ||
|
|
0f88144fd6 | ||
|
|
abe35bddf0 | ||
|
|
cd262cd66c | ||
|
|
0133cc8aa5 | ||
|
|
9883553fb5 | ||
|
|
be5d037ca9 | ||
|
|
21f5fd919d | ||
|
|
159e8fba23 | ||
|
|
d1b2dbd688 | ||
|
|
bebe0c2478 | ||
|
|
61126fb3d5 | ||
|
|
b0886f6b3d | ||
|
|
7fd8eeb1ff | ||
|
|
5c22b43eb5 |
No files matched your search
@@ -32,3 +32,4 @@ trace_data/
|
||||
/src/lib/orderman/perf_analysis/last_pretty_log.csv
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
Vendored
+1
-1
@@ -104,7 +104,7 @@
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
|
||||
@@ -4,6 +4,46 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
## 2.6.1
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
|
||||
|
||||
## 2.6.0
|
||||
### User Visible Changes
|
||||
- In the ladder made last trade more distinctive when overlayed on the volumn column
|
||||
- In the ladder, corrected display of volume tool tip
|
||||
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
|
||||
- Added an option for the Top Algo to delete itself if within N ticks from top of book
|
||||
- Address a rare issue that could lead to an error report on UI exit
|
||||
- Added margin risk limits
|
||||
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
|
||||
- Avoided switching the current account and order type on open ladders when limits are changed
|
||||
### Internal Changes
|
||||
- Ensure that admin logins are always allowed to login, regardless of keycode status.
|
||||
|
||||
## 2.5.0
|
||||
### User Visible Changes
|
||||
- Added loss limit risk check
|
||||
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
|
||||
- Allowed the mini order book defaults for column widths to be changed from the mini order book
|
||||
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
|
||||
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
|
||||
## 2.4.0
|
||||
### User Visible Settings
|
||||
- In the ladder, changed the last trade column to accumulate successive trades at the same price
|
||||
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
|
||||
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
|
||||
- Added ladder settings for the mini order book
|
||||
- Added a safety check to guard against unintentional market crosses
|
||||
- Improved the Queue Holder mode in the spreader:
|
||||
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
|
||||
- Improved spreader performance when in Queue Holder mode
|
||||
### Internal changes
|
||||
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
|
||||
- Addressed a potential concurrency issues with service initialization.
|
||||
|
||||
|
||||
## 2.3.0
|
||||
### User Visible Changes
|
||||
- Introduced multiple settings profiles for the ladder
|
||||
@@ -376,7 +416,8 @@ insert_anchor_links = "right"
|
||||
- This is configurable from a ladder's settings as well
|
||||
### Customized settings in all windows
|
||||
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows␍
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
|
||||
|
||||
|
||||
## 1.3.1 Through 1.3.4
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using bts.api.core;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
readonly string _user_name;
|
||||
public string user_name => _user_name;
|
||||
public readonly UserTuple user;
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
@@ -24,14 +24,14 @@ namespace mbts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import("username")]string user_name) {
|
||||
_user_name = user_name;
|
||||
AdminModel(AdminService service, [Import]UserTuple user) {
|
||||
this.user = user;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
where bts_user.Name == user_name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
|
||||
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
|
||||
select bts_user;
|
||||
|
||||
IEnumerable<BTSAccountImmutable> active_accounts =
|
||||
@@ -62,14 +62,6 @@ namespace mbts.spark {
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
|
||||
|
||||
public UserTuple? get_current_user() {
|
||||
foreach (var kv in _user_map) {
|
||||
// just for good measure, there should never be > 1 element in _user_map, and it should always match _user_name
|
||||
if (kv.Value == _user_name)
|
||||
return new UserTuple(kv.Key, kv.Value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public AccountTuple? get_default_account() {
|
||||
foreach (var kv in _account_map) {
|
||||
return new AccountTuple(kv.Key, kv.Value);
|
||||
|
||||
@@ -3,6 +3,7 @@ using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -22,7 +23,7 @@ namespace bts.ui {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AboutDaemon(IBus bus, [Import("username")]string username) {
|
||||
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
|
||||
_user = username;
|
||||
_bus = bus;
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts.spark
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace bts.spark
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
@@ -82,7 +83,6 @@ namespace bts.spark
|
||||
}
|
||||
}
|
||||
|
||||
//public static async Task<Result<T>>
|
||||
public static async Task<(T value, bool success)> safe_execute<T>(this ErrorReporter error_reporter, Func<Task<Result<T>>> execute, Func<T, bool> get_success, Func<T, string> get_error_msg, string desc) {
|
||||
try {
|
||||
var result = await execute();
|
||||
@@ -101,5 +101,31 @@ namespace bts.spark
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
public static async Task<TResult> safe_execute<T, TResult>(
|
||||
this ErrorReporter error_reporter,
|
||||
Func<Task<Result<T>>> execute,
|
||||
Func<T, bool> get_success,
|
||||
Func<T, string> get_error_msg,
|
||||
Func<T, bool, TResult> result_selector,
|
||||
string desc) {
|
||||
//
|
||||
try {
|
||||
var result = await execute();
|
||||
if (!result.success) {
|
||||
_report_error(result.error_message);
|
||||
} else if (!get_success(result.value)) {
|
||||
_report_error(get_error_msg(result.value));
|
||||
} else {
|
||||
return result_selector(result.value, true);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
_report_error(ex.ToString());
|
||||
}
|
||||
return result_selector(default, false);
|
||||
//
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
using bts.md;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
@@ -27,8 +28,8 @@ namespace bts {
|
||||
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) :
|
||||
base(get_naggle(), MDSourceOptions.DeferInit) {
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
|
||||
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
|
||||
_bus = bus;
|
||||
_token = token;
|
||||
_ss = ss;
|
||||
|
||||
@@ -69,11 +69,11 @@ namespace bts {
|
||||
cpv.Width = double.NaN;
|
||||
cpv.Height = double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
vm.ok_action += () => {
|
||||
window.Close();
|
||||
message.complete(select_result(vm)).execute(_bus);
|
||||
};
|
||||
vm.cancel_action = () => window.Close();
|
||||
vm.cancel_action += () => window.Close();
|
||||
window.move_to_mouse();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.metrics;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -23,7 +24,7 @@ namespace bts.perf {
|
||||
Ping _pinger = new Ping();
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_username = user;
|
||||
|
||||
@@ -5,6 +5,7 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
@@ -18,7 +19,7 @@ namespace bts.shell
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace mbts.spark {
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
@@ -163,7 +163,6 @@ namespace mbts.spark {
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
securities_connectable.Connect(),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -135,6 +136,7 @@ namespace mbts.spark {
|
||||
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
|
||||
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
// commands
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
@@ -151,12 +153,11 @@ namespace mbts.spark {
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -7,6 +7,7 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -17,7 +18,6 @@ using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
@@ -77,12 +77,11 @@ namespace mbts.spark {
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ShowQuantityTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
>
|
||||
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
|
||||
<DataTemplate>
|
||||
@@ -115,6 +116,16 @@
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
|
||||
<DataTemplate>
|
||||
<!-- Note: the motivation for this Template was to enforce a Minimum via the TickUpDownUInt -->
|
||||
<!-- As of 2019/06/17, we believe such a Minimum/Maximum is not actually getting enforced and -->
|
||||
<!-- furthermore is not the appropriate way to go about enforcing said value. It is near the end -->
|
||||
<!-- of the cycle though so that is why we are leaving this Template in here for now until we -->
|
||||
<!-- can come up with a better solution. -->
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}" />
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
|
||||
@@ -240,7 +240,7 @@ 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() { Converter = cell_template_converter });
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -306,17 +306,23 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate MarketDataTypeTemplate { get; set; }
|
||||
public DataTemplate MarketDataCalculationTemplate { get; set; }
|
||||
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
|
||||
public DataTemplate ShowQuantityTemplate { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
Type t = (Type)value;
|
||||
if (t == typeof(double)) return DoubleTemplate;
|
||||
else if (t == typeof(bool)) return BooleanTemplate;
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
else return null;
|
||||
if (value is ICell cell) {
|
||||
if (cell.row is ShowQuantityQuantityModel) {
|
||||
return ShowQuantityTemplate;
|
||||
}
|
||||
Type t = cell.data_type;
|
||||
if (t == typeof(double)) return DoubleTemplate;
|
||||
else if (t == typeof(bool)) return BooleanTemplate;
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -14,12 +15,14 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly SyntheticSecurityService _service;
|
||||
readonly ErrorReporter _error;
|
||||
readonly uint _user_id;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
|
||||
_bus = bus;
|
||||
_service = service;
|
||||
_error = error;
|
||||
_user_id = user_id;
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
|
||||
@@ -39,29 +42,40 @@ namespace mbts.spark {
|
||||
return (result, success);
|
||||
});
|
||||
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
await lookup_strategy_async(security.Id);
|
||||
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
lookup_strategy_async(security.Id);
|
||||
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
|
||||
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
|
||||
if (result.success) {
|
||||
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
|
||||
if (metadata != null) {
|
||||
return metadata.Strategy;
|
||||
}
|
||||
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
|
||||
_error.safe_execute(
|
||||
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
|
||||
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
|
||||
mdc => $"did not find strategy for security id '{id}.'",
|
||||
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
|
||||
"lookup strategy");
|
||||
|
||||
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return null;
|
||||
if (synthetic.Synthetic.UserId != _user_id) {
|
||||
var msynth = synthetic.CloneToMutable();
|
||||
msynth.Synthetic.UserId = _user_id;
|
||||
synthetic = new SecurityImmutable(msynth);
|
||||
}
|
||||
return null;
|
||||
return _error.safe_execute(
|
||||
() => _service.create_synthetic_async(synthetic),
|
||||
r => r.Error == CreateResult.Types.Error.Ok,
|
||||
r => r.Error.ToString(),
|
||||
(r, success) => success ? r.Key : (uint?)null,
|
||||
"creating synthetic");
|
||||
}
|
||||
|
||||
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
|
||||
var result = await _service.create_synthetic_async(synthetic.Value);
|
||||
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
|
||||
}
|
||||
|
||||
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
|
||||
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
|
||||
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
|
||||
}
|
||||
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
_error.safe_execute(
|
||||
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
|
||||
r => r.Error == UpdateResult.Types.Error.Ok,
|
||||
r => r.Error.ToString(),
|
||||
(r, success) => success,
|
||||
"saving strategy");
|
||||
|
||||
public async Task<bool> verify_name_is_unique(string name) {
|
||||
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
|
||||
@@ -108,6 +108,9 @@ namespace bts.shell {
|
||||
NTicks = 2,
|
||||
PriceType = ConditionalQuoting.Types.PriceType.Leg
|
||||
};
|
||||
public static NullableQuantity ShowQuantity => new NullableQuantity {
|
||||
Quantity = 1
|
||||
};
|
||||
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
|
||||
#endregion
|
||||
|
||||
@@ -82,11 +82,10 @@ namespace mbts.spark {
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace mbts.spark {
|
||||
|
||||
public virtual Type data_type { get; } = typeof(T);
|
||||
|
||||
public virtual IRowModel row { get => _model; }
|
||||
|
||||
public T value {
|
||||
get => _model.get_value(_leg_tup);
|
||||
set {
|
||||
@@ -64,6 +66,8 @@ namespace mbts.spark {
|
||||
|
||||
public Type data_type { get; } = typeof(QuantityMeasure);
|
||||
|
||||
public virtual IRowModel row { get => _model; }
|
||||
|
||||
public double ratio {
|
||||
get => _get_qm().Ratio;
|
||||
set {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using bts.shell;
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class ShowQuantityEnabledModel : RowModelBase<bool> {
|
||||
readonly ShowQuantityQuantityModel _quantity;
|
||||
|
||||
public ShowQuantityEnabledModel(ShowQuantityQuantityModel quantity) {
|
||||
_quantity = quantity;
|
||||
}
|
||||
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity != null;
|
||||
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.QuoteParams.ShowQuantity == null) {
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity = SyntheticDataHelper.Defaults.ShowQuantity;
|
||||
_quantity.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity = null;
|
||||
}
|
||||
_quantity.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
internal class ShowQuantityQuantityModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
|
||||
public override string display_name => "Quantity";
|
||||
|
||||
public override string help_text => string.Empty;
|
||||
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup).Quantity = _value_from_store(leg_tup);
|
||||
|
||||
|
||||
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) is NullableQuantity show_quantity ? show_quantity.Quantity : _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
if (_get_value(leg_tup) is NullableQuantity show_quantity) {
|
||||
show_quantity.Quantity = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) != null;
|
||||
|
||||
NullableQuantity _get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark {
|
||||
|
||||
internal interface ICell {
|
||||
event Action value_changed;
|
||||
IRowModel row { get; }
|
||||
Type data_type { get; }
|
||||
bool enabled { get; }
|
||||
}
|
||||
@@ -40,7 +41,7 @@ namespace mbts.spark {
|
||||
FontWeight font_weight { get; }
|
||||
}
|
||||
|
||||
abstract class RowModelBase<T> : ViewModelBase, IRowModel {
|
||||
internal abstract class RowModelBase<T> : ViewModelBase, IRowModel {
|
||||
readonly Dictionary<uint, ReplaySubject<bool>> _enabled_store = new Dictionary<uint, ReplaySubject<bool>>();
|
||||
readonly Dictionary<uint, Subject<T>> _override_value_store = new Dictionary<uint, Subject<T>>();
|
||||
|
||||
@@ -98,6 +99,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private static class Subsections {
|
||||
public static ParameterSection ShowQuantity =>
|
||||
new ParameterSection("Show Quantity", true,
|
||||
"The quantity shown when the order is placed.");
|
||||
|
||||
public static ParameterSection Throttle =>
|
||||
new ParameterSection("Throttle", true,
|
||||
"Reduces re-quoting updates by defining a region on either side " +
|
||||
|
||||
@@ -76,8 +76,8 @@ namespace mbts.spark {
|
||||
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
|
||||
}
|
||||
}));
|
||||
contract_picker_model.cancel_action = _close_new_leg_popup;
|
||||
contract_picker_model.ok_action = () => {
|
||||
contract_picker_model.cancel_action += _close_new_leg_popup;
|
||||
contract_picker_model.ok_action += () => {
|
||||
if (!leg_tuples.Any(l => l.leg.Security.Id == contract_picker_model.result.security.Id)) {
|
||||
leg_tuples.Add(new LegSpreaderParamsTuple(
|
||||
SyntheticDataHelper.Defaults.ContractLeg(contract_picker_model.result.security),
|
||||
@@ -134,7 +134,7 @@ namespace mbts.spark {
|
||||
_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);
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
|
||||
@@ -177,6 +177,8 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
var show_quantity_quantity = new ShowQuantityQuantityModel();
|
||||
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
|
||||
|
||||
@@ -211,6 +213,8 @@ namespace mbts.spark {
|
||||
//new QuoteHiddenModel(),
|
||||
new QuoteQueueHolderModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new ShowQuantityEnabledModel(show_quantity_quantity),
|
||||
show_quantity_quantity,
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using bts.api;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using mbts;
|
||||
|
||||
namespace bts.shell {
|
||||
public class BrowserWindow : IShellWindow {
|
||||
@@ -75,12 +70,15 @@ namespace bts.shell {
|
||||
public string window_type => _window_type;
|
||||
|
||||
public IShellModule Owner {get; set; }
|
||||
|
||||
public ShellWindowUI GetContent() { return new ShellWindowUI(_browser); }
|
||||
|
||||
public void GetState(System.IO.Stream save_here) {
|
||||
_isw.save(new BrowserWindowState() { uri = uri.ToString() }, save_here);
|
||||
}
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
|
||||
public void Dispose() {
|
||||
if (_browser != null) {
|
||||
_browser.Dispose();
|
||||
@@ -102,5 +100,4 @@ namespace bts.shell {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ namespace mbts.spark {
|
||||
} else if (t.definition != null) {
|
||||
return t.definition.subscribe_is_definition_dirty();
|
||||
} else return Observable.Return(false);
|
||||
}).RefCount();
|
||||
});
|
||||
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
|
||||
_vm.save_template(t).ignore();
|
||||
@@ -116,7 +116,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_commands.Add(undo_spark_cmd);
|
||||
_resources.Add(can_undo_source.Subscribe(undo_cmd));
|
||||
_resources.Add(can_undo_source.Connect());
|
||||
// redo
|
||||
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
|
||||
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
|
||||
@@ -133,7 +132,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_commands.Add(redo_spark_cmd);
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
_resources.Add(can_redo_source.Connect());
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace mbts.spark {
|
||||
_persisted_key == other._persisted_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoID && Equals(obj);
|
||||
obj is AlgoID other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
|
||||
+12
-38
@@ -1,5 +1,4 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
@@ -110,38 +109,24 @@ namespace mbts.spark {
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
return await _create_definition(definition, user_id);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.CreateDefinition);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
|
||||
await _create_definition(definition, _admin_model.user.id);
|
||||
|
||||
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.CreateTemplate);
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.UpdateTemplate);
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -175,17 +160,6 @@ namespace mbts.spark {
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
bool _try_get_user(out uint id) {
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
id = user.Value.id;
|
||||
return true;
|
||||
} else {
|
||||
id = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// used for error handling/reporting
|
||||
interface IActionMetadata {
|
||||
string desc { get; }
|
||||
|
||||
+11
-24
@@ -1,14 +1,11 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -27,7 +24,12 @@ namespace mbts.spark {
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
@@ -39,15 +41,11 @@ namespace mbts.spark {
|
||||
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
} else {
|
||||
_report_no_user_id("save template");
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -105,16 +103,5 @@ namespace mbts.spark {
|
||||
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
bool _try_get_user(out uint id) {
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
id = user.Value.id;
|
||||
return true;
|
||||
} else {
|
||||
id = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace mbts.spark {
|
||||
_dispose = new System.Reactive.Disposables.CompositeDisposable(
|
||||
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
|
||||
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).RefCount().Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
Disposable.wrap_collection(algo_map.Values)) {
|
||||
() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace mbts.spark {
|
||||
_model = model;
|
||||
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).RefCount().OnDispatcher());
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
|
||||
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
|
||||
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
@@ -77,7 +77,7 @@ namespace mbts.spark {
|
||||
definitions_view.Refresh();
|
||||
}),
|
||||
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).RefCount().OnDispatcher()
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
|
||||
@@ -22,7 +22,6 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
@@ -129,8 +128,7 @@ namespace mbts.spark {
|
||||
};
|
||||
_rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
// local methods
|
||||
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
|
||||
@@ -181,7 +179,7 @@ namespace mbts.spark {
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
state.SelectManyInSeries(s => _get_observable(bus, model, s));
|
||||
|
||||
static IComparer<FillTuple> _get_column_comparer(Columns col) {
|
||||
|
||||
@@ -6,6 +6,7 @@ using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -67,12 +68,11 @@ namespace mbts.spark {
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -9,6 +9,7 @@ using bts.proto;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using mbts.ui.settings;
|
||||
|
||||
namespace mbts.spark {
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
@@ -220,6 +221,23 @@ namespace mbts.spark {
|
||||
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
readonly struct LadderMiniOrderBookSettings : IEquatable<LadderMiniOrderBookSettings> {
|
||||
// this is the only member now, but more may be added later (that is why a GridSettings member wasn't added directly to LadderSettings).
|
||||
[ProtoMember(1)]
|
||||
public readonly GridSettings value;
|
||||
|
||||
public LadderMiniOrderBookSettings(GridSettings value) =>
|
||||
this.value = value;
|
||||
|
||||
public bool Equals(LadderMiniOrderBookSettings other) => value == null ? other.value == null : value.Equals(other.value);
|
||||
public override bool Equals(object obj) => obj is LadderMiniOrderBookSettings other && Equals(other);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
|
||||
public static bool operator ==(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => a.Equals(b);
|
||||
public static bool operator !=(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
// 1 important reason at this time:
|
||||
// From the settings dialog, default settings will only be persisted when they are NOT EQUAL to the current default settings.
|
||||
@@ -228,6 +246,7 @@ namespace mbts.spark {
|
||||
class LadderSettings : IEquatable<LadderSettings> {
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
const bool _default_show_warn_on_cross = true;
|
||||
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
@@ -255,6 +274,7 @@ namespace mbts.spark {
|
||||
zoom_factor = _default_zoom_factor;
|
||||
use_defaults = true;
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
|
||||
show_warn_on_cross = _default_show_warn_on_cross;
|
||||
}
|
||||
|
||||
public static LadderSettings Default {
|
||||
@@ -319,6 +339,7 @@ namespace mbts.spark {
|
||||
zoom_slider_visible = false,
|
||||
zoom_factor = _default_zoom_factor,
|
||||
net_change = NetChangeTerms.None,
|
||||
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
|
||||
};
|
||||
rv.row_height = LadderRender.measure_row_height(rv);
|
||||
return rv;
|
||||
@@ -460,6 +481,47 @@ namespace mbts.spark {
|
||||
get => _piq_column ?? (_piq_column = LadderSettings.Default.piq_column);
|
||||
set => _piq_column = value;
|
||||
}
|
||||
[ProtoMember(54), DefaultValue(_default_show_warn_on_cross)]
|
||||
public bool show_warn_on_cross { get; set; }
|
||||
|
||||
[ProtoMember(55)]
|
||||
public LadderMiniOrderBookSettings mini_ob { get; set; }
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
// if there are new props, make sure they are not null, invalid, etc.
|
||||
public static LadderSettings sanitize(LadderSettings s) {
|
||||
if (s == null)
|
||||
return null;
|
||||
// these MarketBreakLines are new settings props, ensure they are not null
|
||||
if (s.bid_break_line == null)
|
||||
s.bid_break_line = Default.bid_break_line;
|
||||
if (s.ask_break_line == null)
|
||||
s.ask_break_line = Default.ask_break_line;
|
||||
if (s.mid_break_line == null)
|
||||
s.mid_break_line = Default.mid_break_line;
|
||||
if (s.session_low_line == null)
|
||||
s.session_low_line = Default.session_low_line;
|
||||
if (s.session_high_line == null)
|
||||
s.session_high_line = Default.session_high_line;
|
||||
if (s.session_range_fill == null)
|
||||
s.session_range_fill = Default.session_range_fill;
|
||||
if (s.volume_column == null)
|
||||
s.volume_column = Default.volume_column;
|
||||
if (s.size_display == null)
|
||||
s.size_display = MarketSizeDisplay.get_default();
|
||||
if (s.size_display.normal == null)
|
||||
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
|
||||
if (s.mini_ob.value == null)
|
||||
s.mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
if (s.mini_ob.value.sorting_criterion.Count == 0)
|
||||
s.mini_ob = new LadderMiniOrderBookSettings(s.mini_ob.value.with(sorting_criterion: bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general.sorting_criterion));
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
other != null &&
|
||||
@@ -511,7 +573,9 @@ namespace mbts.spark {
|
||||
zoom_factor == other.zoom_factor &&
|
||||
use_defaults == other.use_defaults &&
|
||||
favorite_algos == other.favorite_algos &&
|
||||
net_change == other.net_change;
|
||||
net_change == other.net_change &&
|
||||
show_warn_on_cross == other.show_warn_on_cross &&
|
||||
mini_ob == other.mini_ob;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderSettings);
|
||||
|
||||
@@ -562,7 +626,8 @@ namespace mbts.spark {
|
||||
zoom_factor.GetHashCode(),
|
||||
use_defaults.GetHashCode(),
|
||||
favorite_algos.GetHashCode(),
|
||||
net_change.GetHashCode());
|
||||
net_change.GetHashCode(),
|
||||
show_warn_on_cross.GetHashCode());
|
||||
|
||||
public struct WindowSpecifics {
|
||||
public readonly double order_entry_panel_width, zoom_factor;
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -81,24 +82,45 @@ namespace bts.shell {
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
// on_shell_window_vm should behave like an event (do not set to null)
|
||||
Action<ShellWindowVM> on_shell_window_vm = _ => { };
|
||||
ShellWindowVM shell_window_vm = null;
|
||||
IShellWindow shell_window = null;
|
||||
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
// resources
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
var profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
|
||||
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, sanitize_ladder_settings(lp.value), lp.security_criteria))),
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
|
||||
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
|
||||
state_settings?.sanitize();
|
||||
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
|
||||
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
|
||||
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
|
||||
bool initialized = false, needs_resync = false;
|
||||
LadderSettings last_value = fit_to_sec(state_settings);
|
||||
var settings_subject = new BehaviorSubject<LadderSettings>(last_value);
|
||||
LadderVM vm = null;
|
||||
var settings_subject = new Subject<LadderSettings>();
|
||||
var settings_source = Observable.Create<LadderSettings>(o => {
|
||||
ensure_synced();
|
||||
if (last_value != null)
|
||||
o.OnNext(last_value);
|
||||
return settings_subject.Subscribe(s => {
|
||||
if (s != null)
|
||||
o.OnNext(s);
|
||||
});
|
||||
});
|
||||
// vm
|
||||
var vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_subject.Where(s => s != null));
|
||||
vm = LadderVM.create(_bus, model, LadderSettings.sanitize(last_value ?? _settings_hub.current.ladder), settings_source);
|
||||
vm.settings_invalidated += on_settings_invalidated;
|
||||
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
|
||||
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
|
||||
vm.request_settings_dialog += open_settings_dialog;
|
||||
bool initialized = false;
|
||||
var settings_subscription = _settings_hub.subscribe_default_settings().Subscribe(on_user_settings);
|
||||
vm.OnDisposed += disposing => {
|
||||
Disposable.dispose(ref settings_subscription);
|
||||
Disposable.dispose(ref active_settings_dialog);
|
||||
Disposable.dispose(ref settings_subject);
|
||||
};
|
||||
@@ -111,8 +133,25 @@ namespace bts.shell {
|
||||
LadderSettings fit_to_sec(LadderSettings s) =>
|
||||
_fit_to_sec(s, md.security.ContractCase);
|
||||
|
||||
void on_settings_invalidated() =>
|
||||
needs_resync = true;
|
||||
|
||||
void ensure_synced() {
|
||||
if (!needs_resync)
|
||||
return;
|
||||
var curr = vm.get_save_state();
|
||||
if (settings_id == 0) {
|
||||
last_value = curr;
|
||||
} else if (!curr.Equals(last_value)) {
|
||||
settings_id = 0;
|
||||
last_value = curr;
|
||||
}
|
||||
needs_resync = false;
|
||||
}
|
||||
|
||||
void on_user_settings(UserSettings us) {
|
||||
if (initialized) {
|
||||
ensure_synced();
|
||||
if (settings_id == 0) {
|
||||
// not on a profile, don't respond to default settings changes
|
||||
return;
|
||||
@@ -168,10 +207,8 @@ namespace bts.shell {
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
var dialog = new ShellSettingsDialog {
|
||||
DataContext = dialog_vm,
|
||||
Title = $"Ladder Settings - '{vm.security}'"
|
||||
};
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
dialog.Closed += on_closed;
|
||||
@@ -221,35 +258,15 @@ namespace bts.shell {
|
||||
}
|
||||
});
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
|
||||
return ShellWindow.create(
|
||||
return shell_window = ShellWindow.create(
|
||||
module, md.security.ToString(), _window_type, view,
|
||||
(v,s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: thread);
|
||||
// if there are new props, make sure they are not null, invalid, etc.
|
||||
LadderSettings sanitize_ladder_settings(LadderSettings s) {
|
||||
// these MarketBreakLines are new settings props, ensure they are not null
|
||||
if (s.bid_break_line == null)
|
||||
s.bid_break_line = LadderSettings.Default.bid_break_line;
|
||||
if (s.ask_break_line == null)
|
||||
s.ask_break_line = LadderSettings.Default.ask_break_line;
|
||||
if (s.mid_break_line == null)
|
||||
s.mid_break_line = LadderSettings.Default.mid_break_line;
|
||||
if (s.session_low_line == null)
|
||||
s.session_low_line = LadderSettings.Default.session_low_line;
|
||||
if (s.session_high_line == null)
|
||||
s.session_high_line = LadderSettings.Default.session_high_line;
|
||||
if (s.session_range_fill == null)
|
||||
s.session_range_fill = LadderSettings.Default.session_range_fill;
|
||||
if (s.volume_column == null)
|
||||
s.volume_column = LadderSettings.Default.volume_column;
|
||||
if (s.size_display == null)
|
||||
s.size_display = MarketSizeDisplay.get_default();
|
||||
if (s.size_display.normal == null)
|
||||
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: thread,
|
||||
vm => {
|
||||
shell_window_vm = vm;
|
||||
title_source.connect();
|
||||
});
|
||||
}
|
||||
|
||||
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
|
||||
@@ -304,4 +321,4 @@ namespace bts.shell {
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark.utilities;
|
||||
using bts.shell.orderbook;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
@@ -293,7 +294,6 @@ namespace mbts.spark {
|
||||
price_filter.minimum.value = real_price;
|
||||
price_filter.maximum.value = real_price;
|
||||
|
||||
var obvm = new OrderBookVM(null, _owner.bus, null, _owner.model.order_gateway, null, true);
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_owner.security),
|
||||
@@ -301,44 +301,22 @@ namespace mbts.spark {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
order_types = AlgoID.exchange_algo_keys
|
||||
},
|
||||
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
|
||||
bts.shell.orderbook.OrderBookStartupData.default_mini_grid.with(_owner.current_settings.mini_ob.value),
|
||||
use_defaults: false
|
||||
);
|
||||
// NOTE:
|
||||
// we might need the ObservableAppend to InsertAt
|
||||
// if these EditXXX ever support settings.
|
||||
|
||||
// The end result after this group of lines will be
|
||||
// ----------------------------------------------
|
||||
// | Cancel | Update | Qty | Price | Commit | Attach | Detach | ...
|
||||
// ----------------------------------------------
|
||||
//
|
||||
var settings_man = new WindowSettingsManager<OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true);
|
||||
_owner.settings.Subscribe(s => settings_man.instance_settings = settings_man.instance_settings.with(s.mini_ob.value));
|
||||
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Price.ToString());
|
||||
var price_index = obvm.columns.indexOf(price_col);
|
||||
if (price_index > -1) {
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditablePrice.ToString());
|
||||
var existing_editprice_index = obvm.columns.indexOf(existing_editprice_col);
|
||||
if (existing_editprice_index == -1) {
|
||||
// insert the edit price column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
|
||||
}
|
||||
}
|
||||
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Size.ToString());
|
||||
var qty_index = obvm.columns.indexOf(qty_col);
|
||||
if (qty_index > -1) {
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditableQty.ToString());
|
||||
var existing_editqty_index = obvm.columns.indexOf(existing_editqty_col);
|
||||
if (existing_editqty_index == -1) {
|
||||
// insert the edit quantity column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
|
||||
}
|
||||
}
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
|
||||
obvm.columns.Insert(0, new OrderBookVM.UpdateColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CancelColumn(nameof(OrderBookRowVM.cancel_command)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
|
||||
|
||||
// the CheckColumn must be inserted at the front of the ObservableAppendCollection,
|
||||
// and not mixed with the other columns in said ObservableAppendCollection's underlying ObservableCollection (the settings dialog uses the underlying collection).
|
||||
// The user cannot adjust this column in the settings dialog, and it should always be the left-most column.
|
||||
obvm.columns.insert_front(new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -40,7 +41,7 @@ namespace mbts.spark {
|
||||
TimeInForce _default_time_in_force;
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear;
|
||||
bool _auto_clear, _show_warn_on_cross;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
@@ -102,6 +103,7 @@ namespace mbts.spark {
|
||||
};
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.execute;
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
|
||||
@@ -126,6 +128,8 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
void dispose_resources() {
|
||||
mini_ob.result_changed -= _update.execute;
|
||||
mini_ob.Dispose();
|
||||
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
|
||||
favorite_algos_vm.Dispose();
|
||||
foreach (var c in _observable_components)
|
||||
@@ -157,6 +161,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
// price format
|
||||
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
|
||||
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
|
||||
@@ -173,6 +178,9 @@ namespace mbts.spark {
|
||||
public bool auto_clear { get => _auto_clear; set => Set(ref _auto_clear, value); }
|
||||
public double order_entry_panel_width { get => _order_entry_panel_width; set => Set(ref _order_entry_panel_width, value); }
|
||||
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
|
||||
// safety checks
|
||||
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
|
||||
|
||||
// favorites
|
||||
public FavoriteAlgosVM favorite_algos_vm { get; }
|
||||
|
||||
@@ -293,6 +301,7 @@ namespace mbts.spark {
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
mini_ob.load_from(settings.mini_ob.value);
|
||||
// columns
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
@@ -340,11 +349,25 @@ namespace mbts.spark {
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
price_format = settings.net_change;
|
||||
// safety checks
|
||||
show_warn_on_cross = settings.show_warn_on_cross;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderSettings get_result() {
|
||||
var clone = SparkUtils.clone_proto(_current);
|
||||
LadderSettings clone;
|
||||
if (_current == null) {
|
||||
clone = new LadderSettings {
|
||||
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
|
||||
};
|
||||
} else {
|
||||
clone = SparkUtils.clone_proto(_current);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
|
||||
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.create());
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
clone.badge_style = badge_style;
|
||||
clone.alternate_order_size = alternate_order_size;
|
||||
clone.favorite_algos = favorite_algos_vm.create();
|
||||
@@ -399,6 +422,8 @@ namespace mbts.spark {
|
||||
clone.cancel_all_visible = _cancel_all_visibility.show;
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.net_change = price_format;
|
||||
// safety checks
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -459,7 +484,7 @@ namespace mbts.spark {
|
||||
_foreground = backing(nameof(foreground), Colors.Black);
|
||||
_background = backing(nameof(background), Colors.White);
|
||||
_font_display_name = backing(nameof(font_display_name),
|
||||
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
|
||||
() => TextUtils.display_name(font_family, font_weight, font_size), new[] { nameof(font_family), nameof(font_weight), nameof(font_size) });
|
||||
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
|
||||
toggle_show_details_cmd = new DelegateCommand(() => {
|
||||
show_details = !show_details;
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace mbts.spark {
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
LadderSettings _current_settings;
|
||||
|
||||
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
@@ -53,7 +52,7 @@ namespace mbts.spark {
|
||||
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_current_settings = init;
|
||||
current_settings = init;
|
||||
this.settings = settings;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
@@ -82,8 +81,8 @@ namespace mbts.spark {
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
/// first things first: assign our <see cref="_current_settings"/>
|
||||
(s => _current_settings = s),
|
||||
/// first things first: assign our <see cref="current_settings"/>
|
||||
(s => current_settings = s),
|
||||
// brush props
|
||||
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
|
||||
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
|
||||
@@ -236,14 +235,13 @@ namespace mbts.spark {
|
||||
}
|
||||
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}),
|
||||
position_src.Connect());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public IBus bus => _bus;
|
||||
|
||||
public event Action on_tick, request_settings_dialog;
|
||||
public event Action on_tick, request_settings_dialog, settings_invalidated;
|
||||
|
||||
public MDConsumerHandle md => _model.md;
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
@@ -278,6 +276,7 @@ namespace mbts.spark {
|
||||
remove => _on_disposed_impl -= value;
|
||||
}
|
||||
public SecurityImmutable security => _model.security;
|
||||
public LadderSettings current_settings { get; private set; }
|
||||
public double high => _high.Value;
|
||||
public double low => _low.Value;
|
||||
public double change => _change.Value;
|
||||
@@ -339,6 +338,8 @@ namespace mbts.spark {
|
||||
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
|
||||
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
|
||||
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
@@ -359,7 +360,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public LadderSettings get_save_state() {
|
||||
var rv = _current_settings.with_window_specifics(
|
||||
var rv = current_settings.with_window_specifics(
|
||||
new LadderSettings.WindowSpecifics(
|
||||
left_panel_width,
|
||||
orders_column_width,
|
||||
@@ -393,18 +394,24 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void on_mini_orderbook_closed(bts.shell.orderbook.OrderBookStartupData ob_state) {
|
||||
current_settings = SparkUtils.clone_proto(current_settings);
|
||||
current_settings.mini_ob = new LadderMiniOrderBookSettings(ob_state.orderbook_grid.general);
|
||||
settings_invalidated?.Invoke();
|
||||
}
|
||||
|
||||
public void send_order((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm == null || args.button == MouseButton.Middle)
|
||||
return;
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && _current_settings.alternate_order_size == 0)) {
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
|
||||
return;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? _current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderVMContainer : ViewModelBase, IDisposable {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
LadderVM _value;
|
||||
SecurityMD? _security_md;
|
||||
HashSet<uint> _selected_order_tree = new HashSet<uint>();
|
||||
|
||||
public LadderVMContainer(IBus bus, OrdersModel model) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
}
|
||||
|
||||
public SecurityMD? security_md {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (_security_md != value) {
|
||||
_security_md = value;
|
||||
_reset_ladder_vm();
|
||||
RaisePropertyChanged(nameof(security_md), nameof(LadderVMContainer.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
public LadderVM value => _value;
|
||||
|
||||
public void Dispose() {
|
||||
if (_value != null) {
|
||||
_value.Dispose();
|
||||
_value = null;
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void set_orders(IEnumerable<uint> orders) {
|
||||
_selected_order_tree = new HashSet<uint>(orders);
|
||||
_reset_ladder_vm();
|
||||
}
|
||||
|
||||
void _reset_ladder_vm() {
|
||||
if (_value == null && !_security_md.HasValue)
|
||||
return;
|
||||
Disposable.replace(ref _value,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,8 @@ namespace mbts.spark {
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
double _old_last_trade_price = 0.0;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
bool _tooltips_are_hooked_up = false;
|
||||
ToolTip tooltip_view = new ToolTip();
|
||||
IDisposable _parameterized_order_popup;
|
||||
@@ -339,7 +340,7 @@ namespace mbts.spark {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
return;
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
show_mbo_popup(point).ignore();
|
||||
} else {
|
||||
@@ -347,6 +348,19 @@ namespace mbts.spark {
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (_vm != null) {
|
||||
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
|
||||
// if warning enabled && (buy above opposite inside market || ask below opposite inside market)
|
||||
// when 1-sided market, _get_top_price will return NaN, and so the price comparison is false
|
||||
// meaning show no warning == correct
|
||||
if (_vm.show_warn_on_cross_from_settings &&
|
||||
(size > 0) &&
|
||||
((bs == Side.Buy && price >= _get_top_price(_asks)) || (bs == Side.Sell && price <= _get_top_price(_bids)))) {
|
||||
string prompt = $"Are you sure you want to CROSS the market to {bs.ToString().ToUpper()} {size} @ {security.spec_or_default().format(price)} ?";
|
||||
if (!BTSMessageBox.Confirm(prompt, "Cross Market Alert", MessageBoxImage.Warning)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command is ICommand cmd && cmd.CanExecute(pbs)) {
|
||||
@@ -680,12 +694,14 @@ namespace mbts.spark {
|
||||
// At any rate, we need this part hiding the tooltip so that we don't leave random tooltips around
|
||||
// and we have to reset to old row so that when we go back to what could be our preivous row, the tooltip gets drawn again
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Volume is 0");
|
||||
_old_tooltip_row = int.MaxValue;
|
||||
_hide_tool_tip();
|
||||
//_old_tooltip_row = Int32.MaxValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
|
||||
_old_tooltip_row = int.MaxValue;
|
||||
_hide_tool_tip();
|
||||
}
|
||||
}
|
||||
@@ -848,21 +864,43 @@ namespace mbts.spark {
|
||||
void _refresh_volume_at_price() {
|
||||
if (!_settings.volume_column.general.show)
|
||||
return;
|
||||
|
||||
// if the last trade has a new size then we need to explicitly
|
||||
// mark the volume container as dirty because there is a chance
|
||||
// we have arrived here off a size change and not a volume change
|
||||
// or a random change, like applying new settings
|
||||
bool last_trade_size_changed = _old_last_trade.HasValue ? _last_trade.size != _old_last_trade.Value.size : true;
|
||||
if (last_trade_size_changed && _settings.volume_column.show_ltq) {
|
||||
_volume_container.mark_dirty();
|
||||
}
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
Log.assert_d(_price_list != null);
|
||||
var ltp = _last_trade.price;
|
||||
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
|
||||
string direction_arrow = string.Empty;
|
||||
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
|
||||
// do nothing if in here b/c direction_arrow already string.empty
|
||||
} else if (ltp > _old_last_trade_price) {
|
||||
direction_arrow = "▲";
|
||||
} else {
|
||||
direction_arrow = "▼";
|
||||
}
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
|
||||
_old_last_trade_price = ltp;
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
|
||||
_old_last_trade = _last_trade;
|
||||
}
|
||||
|
||||
string _get_last_arrow_string() {
|
||||
// if _old_last_trade doesn't have a value, this is the first time the ladder has been launched
|
||||
// and so the direction should be drawn without an arrow since there is no basis for comparison
|
||||
if (!_old_last_trade.HasValue) {
|
||||
_direction_arrow = string.Empty;
|
||||
// if the Lasts are different then we need to reformat the direction arrow in some fashion
|
||||
} else if (_old_last_trade != _last_trade) {
|
||||
var ltp = _last_trade.price;
|
||||
if (MathUtils.epsilon_equals(ltp, _old_last_trade.Value.price)) {
|
||||
// update on the same price, so size must be different
|
||||
_direction_arrow = string.Empty;
|
||||
} else if (ltp > _old_last_trade.Value.price) {
|
||||
_direction_arrow = "▲";
|
||||
} else {
|
||||
_direction_arrow = "▼";
|
||||
}
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_last_trade.size} ";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interactivity;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -156,7 +154,10 @@ namespace mbts.spark {
|
||||
popup.SetBinding(System.Windows.Controls.Primitives.Popup.IsOpenProperty,
|
||||
nameof(OrderBookVM.is_row_collection_populated)
|
||||
);
|
||||
popup.Closed += (sender, event_args) => (popup.DataContext as IDisposable)?.Dispose();
|
||||
popup.Closed += (sender, event_args) => {
|
||||
(_parent.DataContext as LadderVM)?.on_mini_orderbook_closed(obvm.create());
|
||||
(popup.DataContext as IDisposable)?.Dispose();
|
||||
};
|
||||
popup.MouseUp += (sender, event_args) => {
|
||||
if (event_args.ChangedButton == MouseButton.Middle) {
|
||||
popup.IsOpen = false;
|
||||
|
||||
@@ -196,8 +196,7 @@ namespace mbts.spark {
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
NativePriceSize last_trade,
|
||||
string direction_arrow
|
||||
string last_size_and_direction
|
||||
) {
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -216,16 +215,16 @@ namespace mbts.spark {
|
||||
Brush total_brush = null;
|
||||
if (settings.total.bars.result.HasValue) {
|
||||
total_brush = new SolidColorBrush(settings.total.bars.result.Value);
|
||||
} else if (components.Count == 0) {
|
||||
} else if (components.Count == 0 && !settings.show_ltq) {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
double y = row_height * r;
|
||||
var last_trade_here = r == last_index;
|
||||
|
||||
@@ -259,7 +258,8 @@ namespace mbts.spark {
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
|
||||
numbers_text.Add(last_size_and_direction);
|
||||
text_brush = BTSCoreColors.white;
|
||||
}
|
||||
if (numbers_text.Count > 0) {
|
||||
string str = string.Join("/", numbers_text);
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsSampleColumnWidth">153</sys:Double>
|
||||
<!--Margins-->
|
||||
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
|
||||
@@ -190,7 +189,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
@@ -226,7 +225,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
@@ -272,7 +271,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
@@ -329,7 +328,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
@@ -449,7 +448,7 @@
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<TabControl Width="702" MinHeight="275">
|
||||
<TabControl>
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
@@ -458,14 +457,14 @@
|
||||
</TabControl.ItemContainerStyle>
|
||||
<!-- Ladder Columns Tab -->
|
||||
<TabItem Header="Ladder Columns" VerticalAlignment="Stretch">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid Grid.IsSharedSizeScope="True" Margin="8,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="fonts_and_colors_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}" Margin="8,0">
|
||||
<Grid x:Name="fonts_and_colors_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
@@ -473,7 +472,7 @@
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Background" VerticalAlignment="Center"/>
|
||||
@@ -482,8 +481,8 @@
|
||||
<TextBlock Grid.Column="5" HorizontalAlignment="Center" Margin="6,0" Text="Font" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="6" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" Margin="8,0" ItemTemplateSelector="{StaticResource LadderColumnTemplateSelector}"/>
|
||||
<WrapPanel Grid.Row="3" Margin="8,0" Width="{Binding ActualWidth, ElementName=fonts_and_colors_headers, Mode=OneWay}" VerticalAlignment="Top">
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" ItemTemplateSelector="{StaticResource LadderColumnTemplateSelector}"/>
|
||||
<WrapPanel Grid.Row="3" Width="{Binding ActualWidth, ElementName=fonts_and_colors_headers, Mode=OneWay}" VerticalAlignment="Top">
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock" BasedOn="{StaticResource HintText}">
|
||||
@@ -725,7 +724,7 @@
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}" MaxHeight="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -926,6 +925,11 @@
|
||||
</ComboBox>
|
||||
<!--Auto Clear-->
|
||||
<CheckBox Grid.Row="10" Grid.ColumnSpan="3" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Top" Margin="0,2,0,0" FlowDirection="RightToLeft" IsChecked="{Binding auto_clear}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="Path">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
Auto Clear
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
@@ -980,5 +984,29 @@
|
||||
-->
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Safety Checks" VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Content="Warn on Market Cross" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
<usp:GridSettingsView Height="790" Margin="6" DataContext="{Binding mini_ob}" GridSectionContent="{Binding}" GridSectionContentTemplate="{DynamicResource GridSectionCoreContentTemplate}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
@@ -152,20 +152,21 @@ namespace mbts.spark {
|
||||
// When middle click over bid or ask columns:
|
||||
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
|
||||
// Parameterizable order: if qty is null/0, flash and No cursor
|
||||
var pos = e.GetPosition(_host);
|
||||
var column_type = _host._column_for_point(pos).column_type;
|
||||
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
|
||||
if (e.MiddleButton == MouseButtonState.Pressed &&
|
||||
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
|
||||
if (!selected_algo_can_expand) {
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
|
||||
_host._vm.size_calculator.flash_error();
|
||||
_host.Cursor = Cursors.No;
|
||||
if (_host._vm != null) {
|
||||
var pos = e.GetPosition(_host);
|
||||
var column_type = _host._column_for_point(pos).column_type;
|
||||
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
|
||||
if (e.MiddleButton == MouseButtonState.Pressed &&
|
||||
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
|
||||
if (!selected_algo_can_expand) {
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
|
||||
_host._vm.size_calculator.flash_error();
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_is_mouse_down = true;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
@@ -63,9 +63,10 @@
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
mc:Ignorable="d"
|
||||
MaxHeight="240"
|
||||
MinWidth="875"
|
||||
d:DesignHeight="600" d:DesignWidth="1080"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}">
|
||||
|
||||
@@ -1177,7 +1177,6 @@ namespace mbts.spark {
|
||||
public class CommitColumn : FrameworkElementColumnBase<Columns> {
|
||||
public CommitColumn() : base(Columns.Commit) {
|
||||
id = key.ToString();
|
||||
Header = "";
|
||||
Width = 20;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
@@ -38,6 +37,8 @@ namespace mbts.spark {
|
||||
_min_order_params_height = 300,
|
||||
_min_ladder_height = 100,
|
||||
_min_fills_height = 100;
|
||||
static readonly TimeSpan _selection_change_throttle = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
@@ -46,7 +47,8 @@ namespace mbts.spark {
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
readonly SelectedOrderParamsMon _selected_params_mon;
|
||||
readonly OrderParamsAggregator _selected_params_mon;
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly RightPanelModel _right_panel = new RightPanelModel();
|
||||
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
|
||||
double
|
||||
@@ -68,7 +70,6 @@ namespace mbts.spark {
|
||||
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
|
||||
|
||||
public OrderBookVM(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
@@ -77,7 +78,8 @@ namespace mbts.spark {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
|
||||
ladder_vm = new LadderVMContainer(bus, model);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
@@ -145,8 +147,7 @@ namespace mbts.spark {
|
||||
s => fills_vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(fills_vm.columns.Select(c => c.create())),
|
||||
Observable.Return(title)
|
||||
s.with(fills_vm.columns.Select(c => c.create()))
|
||||
),
|
||||
show_save_as_defaults: false);
|
||||
// TODO: bind to main window's Title
|
||||
@@ -154,7 +155,7 @@ namespace mbts.spark {
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
grid_settings_manager = new OrderBookGridSettingsManager(owner_title, bus, notification_service, model, rows, columns);
|
||||
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
|
||||
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
|
||||
IDisposable attach_vm_hooks = null;
|
||||
begin_attaching += on_begin_attaching;
|
||||
@@ -177,6 +178,11 @@ namespace mbts.spark {
|
||||
if (ch.newval && fills_height < _min_fills_height)
|
||||
fills_height = _min_fills_height;
|
||||
}),
|
||||
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().Subscribe(smd => ladder_vm.security_md = smd),
|
||||
_subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
|
||||
var hierarchical_order_keys = new HashSet<uint>(selected_hierarchical_rows.Select(r => r.order_key));
|
||||
ladder_vm.set_orders(hierarchical_order_keys);
|
||||
}),
|
||||
_selected_params_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
|
||||
_selected_params_mon.subscribe_security_plurality().Subscribe(p => {
|
||||
security_plurality = p;
|
||||
@@ -218,7 +224,6 @@ namespace mbts.spark {
|
||||
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
|
||||
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
|
||||
order_mod_vm.algo_vm,
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_params_mon.on_security_md_map),
|
||||
attach_vm,
|
||||
_show_fills.observe_current(true)
|
||||
.Subscribe(is_visible => {
|
||||
@@ -326,8 +331,8 @@ namespace mbts.spark {
|
||||
|
||||
IDisposable connect_order_mod_vm() =>
|
||||
Disposable.Create(
|
||||
_selected_params_mon.subscribe_algos().Subscribe(order_mod_vm.algo_vm.load_many),
|
||||
order_mod_vm.subscribe_is_dirty().Subscribe(_ => {
|
||||
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
|
||||
order_mod_vm.subscribe_is_dirty().OnDispatcher().Subscribe(_ => {
|
||||
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
|
||||
revert_selected_orders_cmd.RaiseCanExecuteChanged();
|
||||
}),
|
||||
@@ -335,10 +340,10 @@ namespace mbts.spark {
|
||||
|
||||
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
|
||||
Disposable.Create(
|
||||
_selected_params_mon.subscribe_security_md().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_params_mon.subscribe_price().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().Subscribe(newval => vm.selected_account = newval));
|
||||
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
|
||||
}
|
||||
|
||||
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -440,6 +445,7 @@ namespace mbts.spark {
|
||||
request_selection?.Invoke((_row_collection, _all_rows_selected));
|
||||
}
|
||||
}
|
||||
public LadderVMContainer ladder_vm { get; }
|
||||
|
||||
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
|
||||
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
|
||||
@@ -447,11 +453,13 @@ namespace mbts.spark {
|
||||
|
||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
||||
try {
|
||||
using (_selected_params_mon.begin_udpate()) {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
if (e.RemovedItems != null) {
|
||||
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = false;
|
||||
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
}
|
||||
@@ -460,11 +468,12 @@ namespace mbts.spark {
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
var root = _get_root(row_vm.order_tuple);
|
||||
row_vm.is_selected = true;
|
||||
_selected_security_mon.add(row_vm.security);
|
||||
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
|
||||
row_vm.updated += on_updated;
|
||||
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
|
||||
}
|
||||
_selected_params_mon.on_order_selected(root);
|
||||
_selected_params_mon.Add(root);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
@@ -476,16 +485,26 @@ namespace mbts.spark {
|
||||
unsubscribe_updated_disposable.Dispose();
|
||||
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
|
||||
}
|
||||
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
return;
|
||||
}
|
||||
var new_root = _get_root(change.newval);
|
||||
if (!new_root.Equals(root)) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.on_order_updated((root, new_root));
|
||||
root = new_root;
|
||||
using (_selected_params_mon.defer_updates()) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.Remove(root);
|
||||
_selected_params_mon.Add(new_root);
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
if (change.oldval.security?.Id != change.newval.security?.Id) {
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_selected_security_mon.remove(change.oldval.security);
|
||||
_selected_security_mon.add(change.newval.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,21 +608,6 @@ namespace mbts.spark {
|
||||
|
||||
public void begin_attach_algos() => is_attaching_orders = true;
|
||||
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
|
||||
_cached_orders.TryGetValue(row.parent_order_key, out parent);
|
||||
|
||||
OrderBookRowVM _get_root_row(OrderBookRowVM row) {
|
||||
while (row.parent_order_key != 0 && _row_collection.get_parent(row.parent_order_key) is OrderBookRowVM prow)
|
||||
row = prow;
|
||||
return row;
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
internal void detach_orders(OrderBookRowVM row) {
|
||||
if (row.is_selected) {
|
||||
_detach_orders_async(selected_rows).ignore();
|
||||
@@ -612,17 +616,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public OrderSecurity get_ordersecurityalgometa(OrderRecordImmutable order) {
|
||||
_cached_orders.TryGetValue(order.Key, out var osam);
|
||||
return osam;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
|
||||
|
||||
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
|
||||
|
||||
internal void show_order_modify(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
|
||||
|
||||
@@ -651,6 +648,12 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
void _on_filter_changed() {
|
||||
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
|
||||
if (!fd.Equals(_cached_filter_data)) {
|
||||
@@ -737,52 +740,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
|
||||
Observable.Create<ISet<uint>>(o => {
|
||||
bool waiting_for_mouse_up = false;
|
||||
var sub_container = new DisposableContainer();
|
||||
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
var selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
|
||||
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
if (waiting_for_mouse_up)
|
||||
return;
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (OrderBookRowVM row in e.NewItems)
|
||||
selected_keys = selected_keys.Add(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (OrderBookRowVM row in e.OldItems)
|
||||
selected_keys = selected_keys.Remove(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
selected_keys = ImmutableHashSet<uint>.Empty;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
waiting_for_mouse_up = true;
|
||||
wait_for_mouse_up();
|
||||
async void wait_for_mouse_up() {
|
||||
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
|
||||
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
waiting_for_mouse_up = false;
|
||||
}
|
||||
} else {
|
||||
o.OnNext(selected_keys);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public class OrderParamsVM : DisposableViewModelBase {
|
||||
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -793,8 +750,6 @@ namespace mbts.spark {
|
||||
AlgoParamPermissions
|
||||
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
HashSet<uint> _selected_order_tree = new HashSet<uint>();
|
||||
LadderVM _ladder_vm;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner)
|
||||
@@ -845,13 +800,7 @@ namespace mbts.spark {
|
||||
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
|
||||
}
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
|
||||
owner._subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
|
||||
_selected_order_tree = new HashSet<uint>(_owner.selected_hierarchical_rows.Select(r => r.order_key));
|
||||
_reset_ladder_vm();
|
||||
})) {
|
||||
() => Disposable.dispose(ref _ladder_vm)
|
||||
};
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
|
||||
// payup methods
|
||||
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
|
||||
void payup_ask(int ticks) => payup(bbo.ask, ticks);
|
||||
@@ -879,8 +828,7 @@ namespace mbts.spark {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (Set(ref _security_md, value)) {
|
||||
_reset_ladder_vm();
|
||||
RaisePropertyChanged(nameof(security), nameof(spec), nameof(ladder_vm));
|
||||
RaisePropertyChanged(nameof(security), nameof(spec));
|
||||
bbo.security_md = value;
|
||||
_payup_bid_cmd.RaiseCanExecuteChanged();
|
||||
_payup_ask_cmd.RaiseCanExecuteChanged();
|
||||
@@ -904,7 +852,6 @@ namespace mbts.spark {
|
||||
}
|
||||
public AlgoDefinitionVM algo_vm { get; }
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
|
||||
public LadderVM ladder_vm => _ladder_vm;
|
||||
public BBOVM bbo { get; }
|
||||
public ICommand send_order { get; }
|
||||
public ICommand payup_bid_cmd => _payup_bid_cmd;
|
||||
@@ -939,17 +886,6 @@ namespace mbts.spark {
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
|
||||
|
||||
void _reset_ladder_vm() {
|
||||
if (_ladder_vm == null && !_security_md.HasValue)
|
||||
return;
|
||||
Disposable.replace(ref _ladder_vm,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_owner._bus,
|
||||
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.Default)) : null);
|
||||
RaisePropertyChanged(nameof(ladder_vm));
|
||||
}
|
||||
void _assign_param_permissions<T>(IAlgoParamVM<T> param, ref AlgoParamPermissions permissions_backing, AlgoParamPermissions permissions) {
|
||||
if (permissions_backing != permissions) {
|
||||
permissions_backing = permissions;
|
||||
@@ -996,128 +932,6 @@ namespace mbts.spark {
|
||||
EqualityComparer.aggregate_hash((int)algo_tree_display_mode, column_filters?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class SelectedOrderParamsMon {
|
||||
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SelectedOrderParamsMon(DynamicOrderHelper dyn_helper) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
|
||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
||||
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
||||
}
|
||||
|
||||
event Action _security_md_map_changed;
|
||||
|
||||
public SecurityImmutable? security => _security_mon.common_value;
|
||||
public SecurityMD? security_md => security is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
||||
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||
public uint? size => _size_mon.common_value_nullable();
|
||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _all_active_mon.common_value;
|
||||
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityMD?> subscribe_security_md() =>
|
||||
Observable.Create<SecurityMD?>(o => {
|
||||
SecurityMD? last_value = security_md;
|
||||
o.OnNext(last_value);
|
||||
_security_mon.common_value_changed += on_common_sec_changed;
|
||||
_security_md_map_changed += refresh;
|
||||
return Disposable.Create(() => {
|
||||
_security_mon.common_value_changed -= on_common_sec_changed;
|
||||
_security_md_map_changed -= refresh;
|
||||
});
|
||||
void on_common_sec_changed(SecurityImmutable? _) => refresh();
|
||||
void refresh() {
|
||||
var curr = security_md;
|
||||
if (curr != last_value)
|
||||
o.OnNext(last_value = curr);
|
||||
}
|
||||
});
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
||||
|
||||
public IDisposable begin_udpate() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
public void on_order_selected(OrderSecurity order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void on_order_unselected(OrderSecurity order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
|
||||
_security_md_map = security_md_map;
|
||||
_security_md_map_changed?.Invoke();
|
||||
}
|
||||
|
||||
public void on_order_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
|
||||
using (begin_udpate()) {
|
||||
_group_mon.remove(ch.oldval);
|
||||
_group_mon.add(ch.newval);
|
||||
}
|
||||
}
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
SecurityMD? _get_sec_md(OrderSecurity osam) =>
|
||||
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
class RightPanelModel {
|
||||
OrderBookRightPanelSettings.ViewMode _mode;
|
||||
OrderBookRightPanelSettings.Components _all_selected, _hidden = OrderBookRightPanelSettings.Components.None;
|
||||
@@ -1180,11 +994,13 @@ namespace mbts.spark {
|
||||
var join = config.Configuration.get<bts.config.JoinAttachUIOptions>();
|
||||
var lean = config.Configuration.get<bts.config.LeanAttachUIOptions>();
|
||||
var top = config.Configuration.get<bts.config.TopAttachUIOptions>();
|
||||
var offset = config.Configuration.get<bts.config.OffsetAttachUIOptions>();
|
||||
_algo_type_conf = new Dictionary<CustomOrderDefinition.CustomOneofCase, (bool, bool)> {
|
||||
{ CustomOrderDefinition.CustomOneofCase.Cover, (cover.show_price, cover.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Join, (join.show_price, join.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Lean, (lean.show_price, lean.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) }
|
||||
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Offset, (offset.show_price, offset.show_quantity) }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -17,10 +17,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.orderbook.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class OrderBookWindow : SparkWindow<OrderBookStartupData>, ISharedWindow {
|
||||
@@ -65,7 +63,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model, _settings_manager);
|
||||
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
@@ -171,14 +169,11 @@ namespace mbts.spark {
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.general = s.general.with(_vm.core_columns.Select(c => c.create()));
|
||||
return _vm.grid_settings_manager.create_vm(s, this.observe_title());
|
||||
return _vm.grid_settings_manager.create_vm(s);
|
||||
}
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
|
||||
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(gs_vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
|
||||
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
|
||||
});
|
||||
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace bts.shell.orderbook {
|
||||
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
|
||||
|
||||
public OrderBookGridSettings with(GridSettings general) =>
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations);
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
|
||||
algo_tree_display_mode = algo_tree_display_mode
|
||||
};
|
||||
|
||||
public bool Equals(OrderBookGridSettings other) =>
|
||||
other != null &&
|
||||
|
||||
+18
-12
@@ -14,16 +14,12 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
|
||||
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
|
||||
|
||||
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
|
||||
AlgoTreeDisplayMode _algo_tree_display_mode;
|
||||
|
||||
public OrderBookGridSettingsManager(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IEnumerable<OrderBookRowVM> rows,
|
||||
ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
static OrderBookGridSettingsManager() {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
// TODO: Remove the immutable font-weight flags when cell animation is ready
|
||||
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
@@ -31,10 +27,20 @@ namespace bts.shell.orderbook {
|
||||
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
// TODO: Remove the immutable font-color flags when cell animation is ready
|
||||
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
|
||||
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
}
|
||||
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
|
||||
hard_column_flags = col_settings_flags;
|
||||
}
|
||||
|
||||
public OrderBookGridSettingsManager(
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IEnumerable<OrderBookRowVM> rows,
|
||||
ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
|
||||
owner_title, bus, model, rows, columns,
|
||||
bus, model, rows, columns,
|
||||
() => new OrderRecordTuplePredicate(),
|
||||
() => {
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
@@ -53,7 +59,7 @@ namespace bts.shell.orderbook {
|
||||
p => p.create_notification_predicate(notification_service.data_cache),
|
||||
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
|
||||
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
|
||||
col_settings_flags);
|
||||
hard_column_flags);
|
||||
_core_man_as_observer = core_manager;
|
||||
}
|
||||
|
||||
@@ -83,8 +89,8 @@ namespace bts.shell.orderbook {
|
||||
return gs;
|
||||
}
|
||||
|
||||
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs, IObservable<string> owner_title) =>
|
||||
new OrderBookSettingsVM(core_manager.create_vm(gs.general, owner_title)) {
|
||||
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
|
||||
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
|
||||
algo_tree_display_mode = gs.algo_tree_display_mode
|
||||
};
|
||||
|
||||
|
||||
@@ -137,12 +137,13 @@ namespace bts.shell.orderbook {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criterion);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
// standard
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
@@ -167,6 +168,13 @@ namespace bts.shell.orderbook {
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// editable
|
||||
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
|
||||
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
|
||||
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
|
||||
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Size, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
|
||||
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
|
||||
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -174,7 +182,7 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
@@ -203,13 +211,26 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> editable {
|
||||
get {
|
||||
yield return cancel;
|
||||
yield return update;
|
||||
yield return edit_quantity;
|
||||
yield return edit_price;
|
||||
yield return commit;
|
||||
yield return attach_detach;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in all) {
|
||||
foreach (var col_setting in editable)
|
||||
yield return col_setting;
|
||||
foreach (var col_setting in standard) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace bts.shell.orderbook {
|
||||
this.core_settings_vm = core_settings_vm;
|
||||
|
||||
public OrderBookSettingsVM(
|
||||
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
Func<OrderRecordTuplePredicate> predicate_data_factory,
|
||||
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
|
||||
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
|
||||
@@ -25,7 +26,7 @@ namespace bts.shell.orderbook {
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
|
||||
owner_title, bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
|
||||
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
|
||||
}
|
||||
|
||||
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
|
||||
|
||||
@@ -554,7 +554,7 @@
|
||||
BorderThickness="0,1,0,0"
|
||||
ResizeBehavior="PreviousAndNext"
|
||||
/>
|
||||
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding order_mod_vm.ladder_vm}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
|
||||
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding ladder_vm.value}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
|
||||
<GridSplitter
|
||||
x:Name="ladder_splitter"
|
||||
Grid.Column="1"
|
||||
|
||||
@@ -117,12 +117,12 @@ namespace bts.shell {
|
||||
var temp_sound_triggers = _sound_trigger_refs;
|
||||
foreach (var kv in temp_sound_triggers) {
|
||||
if (kv.Key.trigger(value)) {
|
||||
sounds.AddRange(kv.Value.keys);
|
||||
sounds.AddRange(kv.Value.Keys);
|
||||
}
|
||||
}
|
||||
var temp_toast_triggers = _toast_trigger_refs;
|
||||
foreach (var krc in temp_toast_triggers) {
|
||||
if (krc.key.trigger(value) is IToastNotification tn && tn.pass)
|
||||
foreach (var kv in temp_toast_triggers) {
|
||||
if (kv.Key.trigger(value) is IToastNotification tn && tn.pass)
|
||||
toast.Add(tn);
|
||||
}
|
||||
if (sounds.Count > 0 || toast.Count > 0)
|
||||
|
||||
@@ -168,17 +168,14 @@ namespace mbts.spark.utilities {
|
||||
// this will be the tag 50 on CME orders
|
||||
// current user might be okay, but we might also want to get it
|
||||
// from the store that serves the risk admin gui
|
||||
ManualOrderIndicator = ManualOrderIndicator.Yes
|
||||
ManualOrderIndicator = ManualOrderIndicator.Yes,
|
||||
User = _admin_model.user.name,
|
||||
UserId = _admin_model.user.id
|
||||
};
|
||||
if (account.HasValue) {
|
||||
rv.Account = account.Value.name;
|
||||
rv.AccountId = account.Value.id;
|
||||
}
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
rv.User = user.Value.name;
|
||||
rv.UserId = user.Value.id;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -605,7 +602,8 @@ namespace mbts.spark.utilities {
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset
|
||||
},
|
||||
// The following spreader types are not yet ready for production use:
|
||||
// CustomOrderDefinition.CustomOneofCase.Dynamic,
|
||||
@@ -622,7 +620,8 @@ namespace mbts.spark.utilities {
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset
|
||||
},
|
||||
outright_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
@@ -718,7 +717,7 @@ namespace mbts.spark.utilities {
|
||||
!b.HasChild);
|
||||
|
||||
public static bool top_equals(TopOrderDefinitionImmutable a, TopOrderDefinitionImmutable b) =>
|
||||
a.WatchLevelTickOffset == b.WatchLevelTickOffset &&
|
||||
a.TicksOff == b.TicksOff &&
|
||||
(a.HasTopThreshold ?
|
||||
b.HasTopThreshold && quantity_measure_equals(a.TopThreshold, b.TopThreshold) :
|
||||
!b.HasTopThreshold) &&
|
||||
@@ -909,7 +908,7 @@ namespace mbts.spark.utilities {
|
||||
add_component(_get_hash(_get_tick_child(tick)));
|
||||
break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top:
|
||||
add_component(def.Top.WatchLevelTickOffset.GetHashCode());
|
||||
add_component(def.Top.TicksOff.GetHashCode());
|
||||
add_component(def.Top.HasTopThreshold ? def.Top.TopThreshold.GetHashCode() : 0);
|
||||
add_component(_get_top_child(def.Top).GetHashCode());
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class OrderParamsAggregator {
|
||||
readonly TimeSpan _throttle;
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
|
||||
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_throttle = throttle;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
|
||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
||||
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
||||
}
|
||||
|
||||
public SecurityImmutable? security => _security_mon.common_value;
|
||||
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
||||
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||
public uint? size => _size_mon.common_value_nullable();
|
||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _all_active_mon.common_value;
|
||||
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
||||
|
||||
public IDisposable defer_updates() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
event Action<TableUpdate<OrderSecurity, uint>> _updated;
|
||||
public void Update(TableUpdate<OrderSecurity, uint> table) {
|
||||
_updated?.Invoke(table);
|
||||
}
|
||||
|
||||
public void Add(OrderSecurity order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void Remove(OrderSecurity order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,13 +105,18 @@ namespace mbts.spark {
|
||||
var inline_account_src = order_properties.AggregateLatest(
|
||||
model.factory.subscribe_accounts(),
|
||||
(last, change, cache) => convert_inline_account(change.New.account, cache),
|
||||
(last, props, change) => convert_inline_account(props.account, change.New),
|
||||
(last, props, change) => convert_inline_account(props.account, change.New, false),
|
||||
() => new InlineUserProperty<AccountTuple>(),
|
||||
new InlineOrderProperties(),
|
||||
DictionaryHelper.empty<uint, string>());
|
||||
// Note that with the .Where on the account_tuples, we will ignore any
|
||||
// updates for which all accounts have been removed.
|
||||
// This was a safer change than changing SelectorProperty's IEnumberable.OnNext
|
||||
// so that it would not reset the selected item on server reboots; i.e. this
|
||||
// change only affects this IObservable.
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
model.factory.subscribe_account_tuples(),
|
||||
model.factory.subscribe_account_tuples().Where(items => items.Any()),
|
||||
inline_account_src,
|
||||
a => new AccountTuple?(a),
|
||||
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
|
||||
@@ -263,9 +268,18 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
}
|
||||
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map) =>
|
||||
//
|
||||
// Regarding the 3rd parameter, force_override_from_inline_property:
|
||||
// When using an InlineUserProperty, in the context of assigning a default vs selected item,
|
||||
// there is somewhat of a conflict between the IObserver<InlineUserProperty<TItem>> and the
|
||||
// IObserver<IEnumerable<TItem>> OnNext methods. This 3rd paramter provides yet another override
|
||||
// to say if the force_override should come from the inline property or if the force_override should
|
||||
// be disbled, by way of the immediate return in ViewModelBase._try_auto_select when force_override
|
||||
// is false. Our intent is then accomplished because we are no longer attempting to set a default/
|
||||
// selected item when the account_tuples potentially changes
|
||||
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map, bool force_override_from_inline_property = true) =>
|
||||
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
|
||||
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), account_id.force_override) :
|
||||
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), force_override_from_inline_property ? account_id.force_override : false) :
|
||||
new InlineUserProperty<AccountTuple>();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,16 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
|
||||
readonly Property<uint?> _ticks_off;
|
||||
readonly Property<QuantityMeasureImmutable?> _top_threshold;
|
||||
|
||||
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_ticks_off = add_nullable_property<uint>(nameof(ticks_off), TopDef.TicksOffFieldNumber);
|
||||
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
|
||||
}
|
||||
|
||||
public uint? ticks_off { get => _ticks_off.Value; set => _ticks_off.set(value); }
|
||||
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.ui.tests
|
||||
{
|
||||
class TestShellWindow : IShellWindow
|
||||
{
|
||||
namespace mbts.ui.tests {
|
||||
class TestShellWindow : IShellWindow {
|
||||
#region IShellWindow boilerplate
|
||||
public string Title { get; set; }
|
||||
public event Action TitleChanged = () => { };
|
||||
@@ -20,6 +15,7 @@ namespace mbts.ui.tests
|
||||
#endregion
|
||||
|
||||
public Dispatcher Dispatcher { get; private set; }
|
||||
|
||||
public ShellWindowUI GetContent() {
|
||||
this.Dispatcher = Dispatcher.CurrentDispatcher;
|
||||
var content = new System.Windows.Forms.Button() { Text = "Click Me!", Width = 100, Height = 100 };
|
||||
@@ -27,10 +23,12 @@ namespace mbts.ui.tests
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; set; }
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
public void Dispose() { IsDisposed = true; }
|
||||
}
|
||||
|
||||
class TestSlowWindow : TestShellWindow, ISlowWindow {
|
||||
public void thread_init() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
using System;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.collections {
|
||||
interface IKeyRefCount<T> {
|
||||
T key { get; }
|
||||
int count { get; }
|
||||
}
|
||||
class RefCountMap<TKey> : IDictionary<TKey, int>, IReadOnlyDictionary<TKey, int> {
|
||||
static readonly Func<KeyRefCount, int> _count_selector = krc => krc.count;
|
||||
|
||||
class RefCountMap<TKey> : IReadOnlyCollection<IKeyRefCount<TKey>>, ICollection {
|
||||
readonly IEqualityComparer<TKey> _key_comparer;
|
||||
readonly Dictionary<TKey, KeyRefCount> _dictionary;
|
||||
readonly IReadOnlyCollection<int> _ro_values;
|
||||
readonly ICollection<int> _values;
|
||||
|
||||
public RefCountMap()
|
||||
: this(EqualityComparer<TKey>.Default) { }
|
||||
@@ -22,31 +22,54 @@ namespace mbts.collections {
|
||||
RefCountMap(Dictionary<TKey, KeyRefCount> dictionary, IEqualityComparer<TKey> key_comparer) {
|
||||
_dictionary = dictionary;
|
||||
_key_comparer = key_comparer;
|
||||
CollectionHelper.wrap_selector(dictionary.Values, _count_selector, out _ro_values, out _values);
|
||||
}
|
||||
|
||||
public int this[TKey key] {
|
||||
get => _dictionary[key].count;
|
||||
set {
|
||||
if (!_dictionary.TryGetValue(key, out var krc))
|
||||
_dictionary[key] = krc = new KeyRefCount(key);
|
||||
krc.count = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _dictionary.Count;
|
||||
public object SyncRoot => null;
|
||||
public IReadOnlyCollection<TKey> Keys => _dictionary.Keys;
|
||||
public IReadOnlyCollection<int> Values => _ro_values;
|
||||
public bool IsReadOnly => false;
|
||||
public bool IsSynchronized => false;
|
||||
public int this[TKey key] => _dictionary[key].count;
|
||||
public IReadOnlyCollection<TKey> keys => _dictionary.Keys;
|
||||
public bool contains_key(TKey key) => _dictionary.ContainsKey(key);
|
||||
public object SyncRoot => null;
|
||||
// explicit
|
||||
ICollection<TKey> IDictionary<TKey, int>.Keys => _dictionary.Keys;
|
||||
ICollection<int> IDictionary<TKey, int>.Values => _values;
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, int>.Keys => _dictionary.Keys;
|
||||
IEnumerable<int> IReadOnlyDictionary<TKey, int>.Values => _values;
|
||||
|
||||
public void CopyTo(Array array, int index) =>
|
||||
((ICollection)_dictionary).CopyTo(array, index);
|
||||
public void Add(TKey key, int value) =>
|
||||
_dictionary.Add(key, new KeyRefCount(key) { count = value });
|
||||
|
||||
public IEnumerator<IKeyRefCount<TKey>> GetEnumerator() =>
|
||||
_dictionary.Values.GetEnumerator();
|
||||
public void Add(KeyValuePair<TKey, int> item) =>
|
||||
_dictionary.Add(item.Key, new KeyRefCount(item.Key) { count = item.Value });
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
GetEnumerator();
|
||||
public void Clear() => _dictionary.Clear();
|
||||
|
||||
public void increment(TKey key) =>
|
||||
increment(key, 1);
|
||||
public RefCountMap<TKey> clone() =>
|
||||
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
|
||||
|
||||
public void increment(TKey key, int count) {
|
||||
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
|
||||
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
|
||||
else krc.count += count;
|
||||
public bool Contains(KeyValuePair<TKey, int> item) =>
|
||||
_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value;
|
||||
|
||||
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, int>[] array, int arrayIndex) {
|
||||
foreach (var kv in _dictionary)
|
||||
array[arrayIndex++] = new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index) {
|
||||
foreach (var kv in _dictionary)
|
||||
array.SetValue(new KeyValuePair<TKey, int>(kv.Key, kv.Value.count), index++);
|
||||
}
|
||||
|
||||
public bool decrement(TKey key) =>
|
||||
@@ -66,38 +89,41 @@ namespace mbts.collections {
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool remove(TKey key) =>
|
||||
_dictionary.Remove(key);
|
||||
public IEnumerator<KeyValuePair<TKey, int>> GetEnumerator() {
|
||||
foreach (var kv in _dictionary)
|
||||
yield return new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
|
||||
}
|
||||
|
||||
public void clear() => _dictionary.Clear();
|
||||
public void increment(TKey key) =>
|
||||
increment(key, 1);
|
||||
|
||||
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
if (try_lookup(key, out key_ref_count)) {
|
||||
remove(key);
|
||||
public void increment(TKey key, int count) {
|
||||
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
|
||||
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
|
||||
else krc.count += count;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) => _dictionary.Remove(key);
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, int> item) {
|
||||
if (_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value) {
|
||||
_dictionary.Remove(item.Key);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
|
||||
key_ref_count = krc;
|
||||
public bool TryGetValue(TKey key, out int value) {
|
||||
if (_dictionary.TryGetValue(key, out var krc)) {
|
||||
value = krc.count;
|
||||
return true;
|
||||
}
|
||||
key_ref_count = null;
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
// explicit
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void absorb(RefCountMap<TKey> other) {
|
||||
foreach (var kv in other._dictionary) {
|
||||
if (!_dictionary.TryGetValue(kv.Key, out KeyRefCount curr))
|
||||
_dictionary.Add(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public RefCountMap<TKey> clone() =>
|
||||
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
|
||||
|
||||
class KeyRefCount : IKeyRefCount<TKey> {
|
||||
class KeyRefCount {
|
||||
public KeyRefCount(TKey key) =>
|
||||
this.key = key;
|
||||
|
||||
@@ -198,27 +224,7 @@ namespace mbts.collections {
|
||||
_dictionary.Clear();
|
||||
}
|
||||
|
||||
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
using (_execute_op()) {
|
||||
if (try_lookup(key, out key_ref_count)) {
|
||||
remove(key);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
using (_execute_op()) {
|
||||
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
|
||||
key_ref_count = krc;
|
||||
return true;
|
||||
}
|
||||
key_ref_count = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class KeyRefCount : IKeyRefCount<TKey> {
|
||||
class KeyRefCount {
|
||||
public KeyRefCount(TKey key) =>
|
||||
this.key = key;
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
@@ -240,84 +238,7 @@ namespace System.Collections.Specialized {
|
||||
}
|
||||
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
|
||||
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
|
||||
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
|
||||
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
|
||||
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
|
||||
/// <returns>An "Observable Heartbeat"</returns>
|
||||
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
|
||||
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
|
||||
IObservable<bool> alive = source.Select(_ => true);
|
||||
return dead.Merge(alive).DistinctUntilChanged();
|
||||
}
|
||||
|
||||
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
|
||||
// with structs it was even more annoying because they can't be null
|
||||
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
|
||||
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
|
||||
|
||||
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
|
||||
source.CombineLatest(second, (v1, v2) => (v1, v2));
|
||||
|
||||
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
|
||||
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
|
||||
PairWithPrevious(source, default);
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
|
||||
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
|
||||
|
||||
/// <summary>
|
||||
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
|
||||
/// each inner sequence is terminated when the next one begins.
|
||||
/// <para/>In other words, observers are notified by the inner observerables in "series," rather than "parallel."
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="select_next"></param>
|
||||
/// <param name="source_sub"></param>
|
||||
/// <returns></returns>
|
||||
public static IConnectableObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
|
||||
new ConnectableSeriesSource<TSource>(source).create_connectable(select_next);
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
|
||||
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
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();
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
if (can_get_value())
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
|
||||
Observable.Create<T>(o => {
|
||||
callback();
|
||||
add_handler(callback);
|
||||
return Disposable.Create(() => remove_handler(callback));
|
||||
void callback() => o.OnNext(selector());
|
||||
});
|
||||
|
||||
static class SparkObservableExtensions {
|
||||
// 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");
|
||||
@@ -326,47 +247,5 @@ namespace System.Reactive.Linq {
|
||||
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
|
||||
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) =>
|
||||
source.Subscribe(new CompositeObserver<T>(observers));
|
||||
|
||||
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
|
||||
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
|
||||
if (buffer_size == 0) return source.Publish().RefCount();
|
||||
int num_subscribers = 0;
|
||||
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
|
||||
IDisposable source_sub = null;
|
||||
return Observable.Create<T>(o => {
|
||||
if (Interlocked.Increment(ref num_subscribers) == 1)
|
||||
source_sub = source.Subscribe(replay.OnNext);
|
||||
IDisposable sub = replay.Subscribe(o);
|
||||
return Disposable.Create(() => {
|
||||
sub.Dispose();
|
||||
if (Interlocked.Decrement(ref num_subscribers) == 0)
|
||||
source_sub.Dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
|
||||
/// </summary>
|
||||
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
|
||||
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
|
||||
|
||||
/// <summary>
|
||||
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
|
||||
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="subscribe_value"></param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
|
||||
IDisposable value_sub = null;
|
||||
return new Disposables.CompositeDisposable(source.Subscribe(val => Disposable.replace(ref value_sub, subscribe_value(val)))) {
|
||||
() => Disposable.dispose(ref value_sub)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ namespace mbts.ui {
|
||||
// module to ask to restore the window after app restart.
|
||||
IShellModule Owner { get; }
|
||||
|
||||
void on_shell_window_vm(ShellWindowVM shell_window_vm);
|
||||
|
||||
// Return the UI controls which will be displayed
|
||||
ShellWindowUI GetContent();
|
||||
|
||||
@@ -113,5 +115,28 @@ namespace mbts.ui {
|
||||
return src.Subscribe(o);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IObservable sequence of <see cref="ShellWindowVM.Title"/>.
|
||||
/// </summary>
|
||||
public static IObservable<string> literal_title_source(IShellWindow shell_window, ShellWindowVM shell_window_vm) {
|
||||
return Observable.Create<string>(o => {
|
||||
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
|
||||
shell_window.TitleChanged += notify;
|
||||
notify();
|
||||
connect();
|
||||
return Disposable.Create(disconnect);
|
||||
void connect() {
|
||||
shell_window_vm.Model.TitleChanged += notify;
|
||||
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
|
||||
}
|
||||
void disconnect() {
|
||||
shell_window_vm.Model.TitleChanged -= notify;
|
||||
shell_window_vm.TitleOverrideChanged -= on_title_override_changed;
|
||||
}
|
||||
void on_title_override_changed(string value) => notify();
|
||||
void notify() => o.OnNext(shell_window_vm.Title);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,12 @@ namespace mbts.persist {
|
||||
try {
|
||||
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
|
||||
if (s != null) {
|
||||
s.ladder.sanitize();
|
||||
// ensure valid named ladder set (w/ distinct, non-zero ids)
|
||||
var ladder_profile_map = new Dictionary<uint, LadderSettingsProfile>();
|
||||
var unidentified_lps = new List<LadderSettingsProfile>();
|
||||
foreach (var lp in s.ladder_profiles) {
|
||||
lp.value.sanitize();
|
||||
if (lp.id == 0) {
|
||||
unidentified_lps.Add(lp);
|
||||
} else ladder_profile_map[lp.id] = lp;
|
||||
|
||||
@@ -40,11 +40,11 @@ namespace bts.utils {
|
||||
public event Action<T> common_value_changed;
|
||||
public event Action<Plurality> plurality_changed;
|
||||
|
||||
public IReadOnlyCollection<T> distinct_values => _values.keys;
|
||||
public T common_value => _values.keys.SafeSingleOrDefault(_default_value);
|
||||
public IReadOnlyCollection<T> distinct_values => _values.Keys;
|
||||
public T common_value => _values.Keys.SafeSingleOrDefault(_default_value);
|
||||
public Plurality plurality {
|
||||
get {
|
||||
switch (_values.keys.Count) {
|
||||
switch (_values.Count) {
|
||||
case 0: return Plurality.None;
|
||||
case 1: return Plurality.One;
|
||||
default: return Plurality.Many;
|
||||
@@ -115,7 +115,7 @@ namespace bts.utils {
|
||||
_deferred_adds = new RefCountMap<T>(value_comparer);
|
||||
_deferred_removes = new RefCountMap<T>(value_comparer);
|
||||
_value_groups = new Dictionary<TKey, RefCountMap<T>>(key_comparer);
|
||||
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.keys);
|
||||
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.Keys);
|
||||
}
|
||||
|
||||
public event Action<KeyValuePair<TKey, IEnumerable<T>>?> common_value_group_changed;
|
||||
@@ -177,27 +177,27 @@ namespace bts.utils {
|
||||
++_deferral_count;
|
||||
return Disposable.Create(() => {
|
||||
if (--_deferral_count == 0 && (_deferred_adds.Count > 0 || _deferred_removes.Count > 0)) {
|
||||
foreach(var krc in _deferred_removes) {
|
||||
var k = _key(krc.key);
|
||||
foreach(var kv in _deferred_removes) {
|
||||
var k = _key(kv.Key);
|
||||
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
|
||||
rcm.decrement(krc.key, krc.count);
|
||||
rcm.decrement(kv.Key, kv.Value);
|
||||
if (rcm.Count == 0)
|
||||
_value_groups.Remove(k);
|
||||
}
|
||||
}
|
||||
foreach (var krc in _deferred_adds) {
|
||||
var k = _key(krc.key);
|
||||
foreach (var kv in _deferred_adds) {
|
||||
var k = _key(kv.Key);
|
||||
if (!_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
|
||||
_value_groups.Add(k, rcm = new RefCountMap<T>(_value_comparer));
|
||||
}
|
||||
rcm.increment(krc.key, krc.count);
|
||||
rcm.increment(kv.Key, kv.Value);
|
||||
}
|
||||
if (!EqualityComparer.nullable_equals(last_fired_common_key, common_value_group?.Key)) {
|
||||
common_value_group_changed?.Invoke(common_value_group);
|
||||
} else if (last_fired_common_key is TKey k) {
|
||||
common_values_changed?.Invoke((
|
||||
_deferred_removes.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k)),
|
||||
_deferred_adds.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k))));
|
||||
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
|
||||
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
|
||||
}
|
||||
_deferred_adds = new RefCountMap<T>(_value_comparer);
|
||||
_deferred_removes = new RefCountMap<T>(_value_comparer);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace mbts.utils {
|
||||
_threshold = value;
|
||||
}
|
||||
}
|
||||
public IReadOnlyCollection<T> filtered_items => _filtered_items.keys;
|
||||
public IReadOnlyCollection<T> filtered_items => _filtered_items.Keys;
|
||||
|
||||
public IDisposable join(T item) =>
|
||||
_subscribe_item == null ? throw new InvalidOperationException("No 'subscribe-item' delegate was specified in the constructor.") : join(item, _subscribe_item(item));
|
||||
|
||||
@@ -820,6 +820,11 @@ namespace mbts.ui.shell {
|
||||
|
||||
void _try_auto_select() {
|
||||
if (_is_user_selected_item) {
|
||||
// if we have a user-selected item and the _desired_item for which we might want to set as the selected item
|
||||
// isn't force_override, then no change
|
||||
if (!_desired_item.force_override) {
|
||||
return;
|
||||
}
|
||||
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out TValue match)) {
|
||||
_set_selected_item(match, SelectionReason.SettingsMandatedSelection);
|
||||
return;
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
x:ClassModifier="internal"
|
||||
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:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:sys="System;assembly=mscorlib"
|
||||
BorderBrush="Black"
|
||||
Padding="3,0,0,0"
|
||||
>
|
||||
@@ -21,10 +16,10 @@
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="19"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Ellipse x:Name="ellipse" Width="19" Height="19" Stroke="{TemplateBinding BorderBrush}"/>
|
||||
<Ellipse x:Name="ellipse" Width="20" Height="20" Stroke="{TemplateBinding BorderBrush}"/>
|
||||
<Path
|
||||
x:Name="arrow"
|
||||
Data="M1,1.5L4.5,5 8,1.5"
|
||||
|
||||
@@ -50,6 +50,16 @@ namespace mbts.ui.controls {
|
||||
LastMessage = 100,
|
||||
ParentKey = 100,
|
||||
AttachedTargetOrderKey = 100;
|
||||
|
||||
public static class MiniOrderBook {
|
||||
public const double
|
||||
Cancel = 37,
|
||||
AttachDetach = 45,
|
||||
Update = 45,
|
||||
EditableSize = 40,
|
||||
EditablePrice = 64,
|
||||
Commit = 20;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ShortColumnWidths {
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace mbts.ui {
|
||||
|
||||
public void set_predicate_animations(SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> predicate_animations) {
|
||||
_active_predicates.Clear();
|
||||
_animation_ref_count.clear();
|
||||
_animation_ref_count.Clear();
|
||||
_row.row_animations.Clear();
|
||||
foreach (var cs in _row.cell_stores)
|
||||
cs.animations.Clear();
|
||||
|
||||
@@ -23,9 +23,10 @@ namespace mbts.spark {
|
||||
/// For flag behavior implementation, see <see cref="GridSettingsVM{TRowData, TPredicateBuilder}.ColumnVM.apply_flags(GridColumnSettingsFeatureFlags)"/>
|
||||
/// </summary>
|
||||
enum GridColumnSettingsFeatureFlags {
|
||||
None,
|
||||
ImmutableFontColor,
|
||||
ImmutableFontWeight
|
||||
None = 0,
|
||||
ImmutableFontColor = 1,
|
||||
ImmutableFontWeight = 1 << 1,
|
||||
ImmutableHeader = 1 << 2
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
|
||||
@@ -38,27 +39,23 @@ namespace mbts.spark {
|
||||
public class ColumnVM : DisposableViewModelBase, IColumnVM {
|
||||
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
|
||||
|
||||
readonly Property<string> _id, _display_name;
|
||||
readonly Property<double> _width;
|
||||
readonly Property<HorizontalAlignment> _horizontal_alignment;
|
||||
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
|
||||
readonly Property<FontWeight> _font_weight_override;
|
||||
string
|
||||
_id = string.Empty,
|
||||
_display_name = string.Empty;
|
||||
double _width = 70;
|
||||
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
|
||||
bool
|
||||
_is_visible,
|
||||
_can_override_display_name = true,
|
||||
_can_override_font_color = true,
|
||||
_is_font_color_overridden,
|
||||
_can_override_font_weight = true,
|
||||
_is_font_weight_overridden;
|
||||
FontWeight _font_weight_override;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
|
||||
this.owner = owner;
|
||||
_id = backing(nameof(id), string.Empty);
|
||||
_display_name = backing(nameof(display_name), string.Empty);
|
||||
_width = backing(nameof(width), 70.0);
|
||||
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
|
||||
_is_visible = backing(nameof(is_visible), false);
|
||||
// font color override
|
||||
_can_override_font_color = backing(nameof(can_override_font_color), true);
|
||||
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
|
||||
// font weight override
|
||||
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
|
||||
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
|
||||
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
|
||||
// animations
|
||||
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
|
||||
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
|
||||
@@ -66,24 +63,29 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
|
||||
}
|
||||
|
||||
public string id => _id.Value;
|
||||
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
|
||||
public double width { get => _width.Value; set => _width.set(value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
|
||||
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
|
||||
public string id { get => _id; private set => Set(ref _id, value); }
|
||||
public double width { get => _width; set => Set(ref _width, value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
|
||||
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
|
||||
// display name
|
||||
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
|
||||
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
|
||||
// font color override
|
||||
public bool can_override_font_color => _can_override_font_color.Value;
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
|
||||
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight override
|
||||
public bool can_override_font_weight => _can_override_font_weight.Value;
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
|
||||
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
// animations
|
||||
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
|
||||
|
||||
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
|
||||
var settings = _settings.with(display_name, is_visible, null,
|
||||
var settings = _settings.with(
|
||||
can_override_display_name ? display_name : null,
|
||||
is_visible,
|
||||
null,
|
||||
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : default,
|
||||
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : default,
|
||||
width,
|
||||
@@ -94,19 +96,21 @@ namespace mbts.spark {
|
||||
|
||||
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
|
||||
_settings = value.settings;
|
||||
_id.set(_settings.id);
|
||||
_display_name.set(_settings.display_name);
|
||||
_width.set(_settings.width);
|
||||
_horizontal_alignment.set(_settings.horizontal_alignment);
|
||||
_is_visible.set(_settings.is_visible);
|
||||
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
|
||||
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override.set(_settings.font_weight_override.Value);
|
||||
}
|
||||
if (_settings.font_color_override.HasValue) {
|
||||
id = _settings.id;
|
||||
display_name = _settings.display_name;
|
||||
width = _settings.width;
|
||||
horizontal_alignment = _settings.horizontal_alignment;
|
||||
is_visible = _settings.is_visible;
|
||||
// font color
|
||||
is_font_color_overridden = _settings.font_color_override.HasValue;
|
||||
if (is_font_color_overridden) {
|
||||
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
|
||||
}
|
||||
// font weight
|
||||
is_font_weight_overridden = _settings.font_weight_override.HasValue;
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override = _settings.font_weight_override.Value;
|
||||
}
|
||||
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
|
||||
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
|
||||
vm.load_from((g.Key, g));
|
||||
@@ -115,15 +119,16 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
|
||||
var split_flags = EnumHelper.split_flags(flags).ToList();
|
||||
// display name
|
||||
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
|
||||
if (!can_override_display_name)
|
||||
display_name = id;
|
||||
// font color
|
||||
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
|
||||
if (!can_override_font_color)
|
||||
_is_font_color_overridden.set(false);
|
||||
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
|
||||
is_font_color_overridden &= can_override_font_color;
|
||||
// font weight
|
||||
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
|
||||
if (!can_override_font_weight)
|
||||
_is_font_weight_overridden.set(false);
|
||||
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
|
||||
is_font_weight_overridden &= can_override_font_weight;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{display_name} ({id})";
|
||||
|
||||
@@ -60,21 +60,17 @@ namespace mbts.ui.settings {
|
||||
[DefaultValue(_default_font_color)]
|
||||
readonly uint _font_color;
|
||||
[ProtoMember(5)]
|
||||
[DefaultValue(default(string))]
|
||||
readonly ColorOpacityProto _selection_highlight;
|
||||
[ProtoMember(6)]
|
||||
readonly List<GridColumnSettings> _columns;
|
||||
List<GridColumnSettings> _columns;
|
||||
[ProtoMember(7)]
|
||||
readonly List<SortingCriteria> _sorting_criterion;
|
||||
List<SortingCriteria> _sorting_criterion;
|
||||
|
||||
public GridSettings() {
|
||||
_font_family = _default_font_family;
|
||||
_font_size = _default_font_size;
|
||||
_font_weight = _default_font_weight;
|
||||
_font_color = _default_font_color;
|
||||
_selection_highlight = default;
|
||||
_columns = new List<GridColumnSettings>();
|
||||
_sorting_criterion = new List<SortingCriteria>();
|
||||
}
|
||||
|
||||
public GridSettings(
|
||||
@@ -95,9 +91,9 @@ namespace mbts.ui.settings {
|
||||
public uint font_color => _font_color;
|
||||
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
|
||||
public ColorOpacityProto selection_highlight => _selection_highlight;
|
||||
public IReadOnlyList<GridColumnSettings> columns => _columns;
|
||||
public IReadOnlyList<GridColumnSettings> columns => _columns ?? (_columns = new List<GridColumnSettings>(0));
|
||||
[Obsolete] // TODO: remove sorting_criterion from GridSettings entirely. This should be part of IShellWindow's StartupData, since it is an instance-specific setting, and prob not part of the shared defaults
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion;
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = new List<SortingCriteria>(0));
|
||||
|
||||
public static GridSettings get_default() =>
|
||||
new GridSettings(_default_font_family, _default_font_size, FontWeights.Normal, _default_font_color, new ColorOpacityProto(Colors.MediumSlateBlue, 0.4));
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace mbts.ui {
|
||||
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>> _predicate_animations_data;
|
||||
|
||||
public GridSettingsManager(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
IEnumerable<TRow> rows,
|
||||
@@ -90,8 +89,8 @@ namespace mbts.ui {
|
||||
public SolidColorBrush font_color => _font_color.Value;
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
|
||||
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings, IObservable<string> title) {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(title, _bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(_bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load(_create_settings_obj(settings, _predicate_animations_data));
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -54,14 +54,15 @@ namespace mbts.spark {
|
||||
readonly Property<IColumnVM> _current_column;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<string> _font_display_name, _owner_title;
|
||||
readonly Property<string> _font_display_name;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
|
||||
|
||||
public GridSettingsVM(
|
||||
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
Func<TPredicateData> predicate_data_factory,
|
||||
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
|
||||
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
|
||||
@@ -69,7 +70,6 @@ namespace mbts.spark {
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
//
|
||||
_owner_title = backing(nameof(owner_title), owner_title);
|
||||
this.bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_predicate_data_factory = predicate_data_factory;
|
||||
@@ -80,7 +80,7 @@ namespace mbts.spark {
|
||||
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
|
||||
_font_size = backing(nameof(font_size), FontSizes.Small);
|
||||
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), nameof(font_family), nameof(font_size));
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
|
||||
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
|
||||
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
|
||||
_current_column = backing<IColumnVM>(nameof(current_column));
|
||||
@@ -92,7 +92,6 @@ namespace mbts.spark {
|
||||
public event EventHandler RequestClose;
|
||||
public event Action committed;
|
||||
|
||||
public string owner_title => _owner_title.Value;
|
||||
public IBus bus { get; }
|
||||
public ObservableCollection<IColumnVM> columns { get; } = new ObservableCollection<IColumnVM>();
|
||||
public IColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
|
||||
|
||||
@@ -227,11 +227,23 @@
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:PredicateTemplateKeys.SecurityCriteria}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
SelectedCriteria="{Binding value}"
|
||||
AllowEmpty="True"
|
||||
CloseOnOK="True"
|
||||
CloseOnCancel="True"
|
||||
MaxWidth="180"
|
||||
SelectedCriteria="{Binding value, UpdateSourceTrigger=Explicit}"
|
||||
SecurityMap="{Binding Path='[security_map]', UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
|
||||
Header="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}}"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
Header="{Binding value}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding value, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
|
||||
>
|
||||
<c:ContractPickerPopup.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
|
||||
</DataTemplate>
|
||||
</c:ContractPickerPopup.HeaderTemplate>
|
||||
</c:ContractPickerPopup>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
@@ -550,13 +562,35 @@
|
||||
</c:DragDropListBox.ItemContainerStyle>
|
||||
<c:DragDropListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="column_item_content_root">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager>
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.can_override_display_name, Source={u:DirectReference column_item_content_root}, Converter={x:Static usp:GridSettingsView.ColumnVisibilityToggleVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="visibility_toggle" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="visibility_toggle" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="(N/A)"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox IsChecked="{Binding is_visible}" />
|
||||
<CheckBox x:Name="visibility_toggle" ToolTipService.ShowOnDisabled="True" IsChecked="{Binding is_visible}"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding id, Converter={x:Static ut:DisplayNameHelper.CamelCaseSpacesConverter}}"/>
|
||||
<TextBlock Margin="2,0,0,0" FontStyle="Italic" Text="{Binding display_name, Mode=OneWay, StringFormat='{} ({0})'}">
|
||||
@@ -576,7 +610,29 @@
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="{Binding current_column.display_name, StringFormat='{}\'{0}\' Cell Appearance'}"/>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid x:Name="column_content_root">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager>
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.current_column.can_override_display_name, Source={u:DirectReference column_content_root}, Converter={x:Static usp:GridSettingsView.DisplayNameVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="display_name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="display_name_text_box" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="(N/A)"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
@@ -594,7 +650,7 @@
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Header</TextBlock>
|
||||
<TextBox Grid.Column="2" Text="{Binding current_column.display_name}"/>
|
||||
<TextBox x:Name="display_name_text_box" Grid.Column="2" ToolTipService.ShowOnDisabled="True" Text="{Binding current_column.display_name}"/>
|
||||
<TextBlock Grid.Row="2">Width</TextBlock>
|
||||
<c:TickUpDownDouble Grid.Row="2" Grid.Column="2" Value="{Binding current_column.width}"/>
|
||||
<TextBlock Grid.Row="4">Horizontal Alignment</TextBlock>
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for GridSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class GridSettingsView : UserControl {
|
||||
public static readonly IValueConverter
|
||||
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
|
||||
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
|
||||
|
||||
public GridSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -38,6 +42,24 @@ namespace mbts.ui.spark {
|
||||
ValueConverter.create<string, string, Visibility>((id, header) => id == header ? Visibility.Collapsed : Visibility.Visible);
|
||||
|
||||
public bool EnableSoundTriggers { get; set; }
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class DisplayName {
|
||||
public const string GroupName = nameof(DisplayName);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class ColumnVisibilityToggle {
|
||||
public const string GroupName = nameof(ColumnVisibilityToggle);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class PredicateTemplateKeys {
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace mbts.ui {
|
||||
|
||||
public static ImmutableColumnSettingStore create(GridColumnSettingsFeatureFlags flags) {
|
||||
var stores_per_flag = new List<ImmutableColumnSettingStore>();
|
||||
if ((flags & GridColumnSettingsFeatureFlags.ImmutableHeader) != 0) {
|
||||
stores_per_flag.Add(new ImmutableHeaderOverrideStore());
|
||||
}
|
||||
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) != 0) {
|
||||
stores_per_flag.Add(new ImmutableFontColorOverrideStore());
|
||||
}
|
||||
@@ -24,6 +27,19 @@ namespace mbts.ui {
|
||||
public abstract GridColumnSettings apply(GridColumnSettings settings);
|
||||
public abstract void store(GridColumnSettings settings);
|
||||
|
||||
class ImmutableHeaderOverrideStore : ImmutableColumnSettingStore {
|
||||
string _header;
|
||||
|
||||
public ImmutableHeaderOverrideStore()
|
||||
: base(GridColumnSettingsFeatureFlags.ImmutableHeader) { }
|
||||
|
||||
public override GridColumnSettings apply(GridColumnSettings settings) =>
|
||||
settings.with(display_name: _header);
|
||||
|
||||
public override void store(GridColumnSettings settings) =>
|
||||
_header = settings.display_name;
|
||||
}
|
||||
|
||||
class ImmutableFontColorOverrideStore : ImmutableColumnSettingStore {
|
||||
NullableColorProto _value;
|
||||
|
||||
@@ -69,4 +85,4 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,6 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -13,45 +10,45 @@ namespace mbts.spark {
|
||||
public class SimpleColumnVM : ViewModelBase, ISimpleColumnVM {
|
||||
public readonly SimpleGridSettingsVM owner;
|
||||
|
||||
readonly Property<string> _id, _display_name;
|
||||
readonly Property<double> _width;
|
||||
readonly Property<HorizontalAlignment> _horizontal_alignment;
|
||||
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
|
||||
readonly Property<FontWeight> _font_weight_override;
|
||||
string
|
||||
_id = string.Empty,
|
||||
_display_name = string.Empty;
|
||||
double _width = 70;
|
||||
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
|
||||
bool
|
||||
_is_visible,
|
||||
_can_override_display_name = true,
|
||||
_can_override_font_color = true,
|
||||
_is_font_color_overridden,
|
||||
_can_override_font_weight = true,
|
||||
_is_font_weight_overridden;
|
||||
FontWeight _font_weight_override;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public SimpleColumnVM(SimpleGridSettingsVM owner) {
|
||||
public SimpleColumnVM(SimpleGridSettingsVM owner) =>
|
||||
this.owner = owner;
|
||||
_id = backing(nameof(id), string.Empty);
|
||||
_display_name = backing(nameof(display_name), string.Empty);
|
||||
_width = backing(nameof(width), 70.0);
|
||||
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
|
||||
_is_visible = backing(nameof(is_visible), false);
|
||||
// font color override
|
||||
_can_override_font_color = backing(nameof(can_override_font_color), true);
|
||||
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
|
||||
// font weight override
|
||||
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
|
||||
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
|
||||
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
|
||||
}
|
||||
|
||||
public string id => _id.Value;
|
||||
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
|
||||
public double width { get => _width.Value; set => _width.set(value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
|
||||
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
|
||||
public string id { get => _id; private set => Set(ref _id, value); }
|
||||
public double width { get => _width; set => Set(ref _width, value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
|
||||
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
|
||||
// display name
|
||||
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
|
||||
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
|
||||
// font color override
|
||||
public bool can_override_font_color => _can_override_font_color.Value;
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
|
||||
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight override
|
||||
public bool can_override_font_weight => _can_override_font_weight.Value;
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
|
||||
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
|
||||
public GridColumnSettings create() =>
|
||||
_settings.with(display_name, is_visible, null,
|
||||
_settings.with(
|
||||
can_override_display_name ? display_name : null,
|
||||
is_visible,
|
||||
null,
|
||||
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : (NullableFontWeightProto?)null,
|
||||
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : (NullableColorProto?)null,
|
||||
width,
|
||||
@@ -59,31 +56,34 @@ namespace mbts.spark {
|
||||
|
||||
public void load_from(GridColumnSettings value) {
|
||||
_settings = value;
|
||||
_id.set(_settings.id);
|
||||
_display_name.set(_settings.display_name);
|
||||
_width.set(_settings.width);
|
||||
_horizontal_alignment.set(_settings.horizontal_alignment);
|
||||
_is_visible.set(_settings.is_visible);
|
||||
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
|
||||
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override.set(_settings.font_weight_override.Value);
|
||||
}
|
||||
id = value.id;
|
||||
display_name = value.display_name;
|
||||
width = value.width;
|
||||
horizontal_alignment = value.horizontal_alignment;
|
||||
is_visible = value.is_visible;
|
||||
// font color
|
||||
is_font_color_overridden = value.font_color_override.HasValue;
|
||||
if (is_font_color_overridden) {
|
||||
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
|
||||
}
|
||||
// font weight
|
||||
is_font_weight_overridden = value.font_weight_override.HasValue;
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override = _settings.font_weight_override.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
|
||||
var split_flags = EnumHelper.split_flags(flags).ToList();
|
||||
// display name
|
||||
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
|
||||
if (!can_override_display_name)
|
||||
display_name = id;
|
||||
// font color
|
||||
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
|
||||
if (!can_override_font_color)
|
||||
_is_font_color_overridden.set(false);
|
||||
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
|
||||
is_font_color_overridden &= can_override_font_color;
|
||||
// font weight
|
||||
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
|
||||
if (!can_override_font_weight)
|
||||
_is_font_weight_overridden.set(false);
|
||||
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
|
||||
is_font_weight_overridden &= can_override_font_weight;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{display_name} ({id})";
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace mbts.ui {
|
||||
public SolidColorBrush font_color => _font_color.Value;
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
|
||||
public SimpleGridSettingsVM create_vm(GridSettings settings, IObservable<string> title) {
|
||||
var vm = new SimpleGridSettingsVM(title, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
public SimpleGridSettingsVM create_vm(GridSettings settings) {
|
||||
var vm = new SimpleGridSettingsVM(_column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load_from(settings);
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
@@ -29,27 +34,44 @@ namespace mbts.spark {
|
||||
readonly Property<ISimpleColumnVM> _current_column;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<string> _font_display_name, _owner_title;
|
||||
readonly Property<string> _font_display_name;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
|
||||
readonly DeferrableActionWrapper _update;
|
||||
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
|
||||
|
||||
public SimpleGridSettingsVM(IObservable<string> owner_title, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
public SimpleGridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
|
||||
_owner_title = backing(nameof(owner_title), owner_title);
|
||||
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
|
||||
_font_size = backing(nameof(font_size), FontSizes.Small);
|
||||
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), nameof(font_family), nameof(font_size));
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
|
||||
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
|
||||
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
|
||||
_current_column = backing<ISimpleColumnVM>(nameof(current_column));
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_hooks(columns, c => {
|
||||
if (c is INotifyPropertyChanged npc) {
|
||||
npc.PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => npc.PropertyChanged -= on_prop_changed);
|
||||
} else return Disposable.empty;
|
||||
})) {
|
||||
() => {
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged -= on_prop_changed;
|
||||
}
|
||||
};
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
_update.execute();
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
public event Action committed;
|
||||
public event Action committed, result_changed;
|
||||
|
||||
public string owner_title => _owner_title.Value;
|
||||
public ObservableCollection<ISimpleColumnVM> columns { get; } = new ObservableCollection<ISimpleColumnVM>();
|
||||
public ISimpleColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
|
||||
public string font_display_name => _font_display_name.Value;
|
||||
@@ -72,29 +94,41 @@ namespace mbts.spark {
|
||||
|
||||
public string key => ui.spark.GridSettingsTemplateKeys.SimpleGrid;
|
||||
|
||||
IEnumerable<INotifyPropertyChanged> _observable_components {
|
||||
get {
|
||||
yield return font_color;
|
||||
yield return selection_highlight;
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public GridSettings create() =>
|
||||
new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), columns.Select(c => c.create()));
|
||||
|
||||
public void load_from(GridSettings value) {
|
||||
_font_family.set(new FontFamily(value.font_family));
|
||||
_font_weight.set(value.font_weight);
|
||||
font_color.load_from(value.font_color.rgb_solid_brush());
|
||||
selection_highlight.load_from(value.selection_highlight.to_brush());
|
||||
_font_size.set(value.font_size);
|
||||
var curr_col_id = current_column?.id;
|
||||
columns.SetItems(value.columns.Select(col => {
|
||||
var vm = new SimpleColumnVM(this);
|
||||
vm.load_from(col);
|
||||
// reapply our favorite column flags
|
||||
// this comes in handy when performing Load From Factory - the editor will contain
|
||||
// a consistent view of what can be overridden and what cannot
|
||||
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
|
||||
vm.apply_flags(flags);
|
||||
return vm;
|
||||
}));
|
||||
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out ISimpleColumnVM curr_column))
|
||||
curr_column = columns.FirstOrDefault();
|
||||
_current_column.set(curr_column);
|
||||
if (value == null)
|
||||
return;
|
||||
using (_update.defer_execution()) {
|
||||
_font_family.set(new FontFamily(value.font_family));
|
||||
_font_weight.set(value.font_weight);
|
||||
font_color.load_from(value.font_color.rgb_solid_brush());
|
||||
selection_highlight.load_from(value.selection_highlight.to_brush());
|
||||
_font_size.set(value.font_size);
|
||||
var curr_col_id = current_column?.id;
|
||||
columns.SetItems(value.columns.Select(col => {
|
||||
var vm = new SimpleColumnVM(this);
|
||||
vm.load_from(col);
|
||||
// reapply our favorite column flags
|
||||
// this comes in handy when performing Load From Factory - the editor will contain
|
||||
// a consistent view of what can be overridden and what cannot
|
||||
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
|
||||
vm.apply_flags(flags);
|
||||
return vm;
|
||||
}));
|
||||
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out ISimpleColumnVM curr_column))
|
||||
curr_column = columns.FirstOrDefault();
|
||||
_current_column.set(curr_column);
|
||||
}
|
||||
}
|
||||
|
||||
void IWindowSettingsVM<GridSettings>.load(GridSettings value) => load_from(value);
|
||||
|
||||
@@ -37,7 +37,9 @@ namespace mbts.ui {
|
||||
#endregion
|
||||
|
||||
public static bool Confirm(string message, string title) =>
|
||||
Show(message, title, BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, MessageBoxImage.Question, true, true, true) == BTSMessageBoxButtons.Yes;
|
||||
Confirm(message, title, MessageBoxImage.Question);
|
||||
public static bool Confirm(string message, string title, MessageBoxImage icon) =>
|
||||
Show(message, title, BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon, true, true, true) == BTSMessageBoxButtons.Yes;
|
||||
|
||||
public static void Error(string message, string title) =>
|
||||
Show(message, title, BTSMessageBoxButtons.OK, MessageBoxImage.Error, true, true, true);
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace mbts.ui.controls
|
||||
public DragDropListBox() {
|
||||
Loaded += (s, e) => {
|
||||
var al = AdornerLayer.GetAdornerLayer(this);
|
||||
al.Add(_adorner = new DragIndicatorAdorner<DraggableListBoxItem>(this));
|
||||
al?.Add(_adorner = new DragIndicatorAdorner<DraggableListBoxItem>(this));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
using System;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.ui.controls
|
||||
{
|
||||
namespace mbts.ui.controls {
|
||||
public partial class HierarchicalLaunchMenu : UserControl, IShellWindow
|
||||
{
|
||||
public HierarchicalLaunchMenu(object context) {
|
||||
@@ -36,27 +27,7 @@ namespace mbts.ui.controls
|
||||
public ShellWindowUI GetContent() {
|
||||
return new ShellWindowUI(this);
|
||||
}
|
||||
|
||||
//bool _tree_was_clicked = false;
|
||||
//private async void _tree_clicked(object sender, RoutedEventArgs e)
|
||||
//{
|
||||
// e.Handled = true;
|
||||
|
||||
// if (_tree_was_clicked) return;
|
||||
// _tree_was_clicked = true;
|
||||
// DependencyObject source = e.OriginalSource as DependencyObject;
|
||||
|
||||
// while (source != null && !(source is TreeViewItem))
|
||||
// source = VisualTreeHelper.GetParent(source);
|
||||
|
||||
// TreeViewItem tvi = source as TreeViewItem;
|
||||
// if (tvi != null)
|
||||
// tvi.IsExpanded = !tvi.IsExpanded;
|
||||
|
||||
// await Task.Delay(1000);
|
||||
// _tree_was_clicked = false;
|
||||
|
||||
//}
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
}
|
||||
|
||||
public class HierarchicalViewModel : ViewModelBase
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.ui.controls
|
||||
{
|
||||
public partial class MenuLaunchMenu : UserControl, IShellWindow
|
||||
{
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public partial class MenuLaunchMenu : UserControl, IShellWindow {
|
||||
public ObservableCollection<MenuLaunchMenuViewModel> MenuItems { get; set; }
|
||||
public MenuLaunchMenu(object context)
|
||||
{
|
||||
@@ -39,11 +25,10 @@ namespace mbts.ui.controls
|
||||
public ShellWindowUI GetContent() {
|
||||
return new ShellWindowUI(this);
|
||||
}
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
}
|
||||
|
||||
public class MenuLaunchMenuViewModel : ViewModelBase
|
||||
{
|
||||
public class MenuLaunchMenuViewModel : ViewModelBase {
|
||||
public string Header { get; set; }
|
||||
|
||||
public ObservableCollection<MenuLaunchMenuViewModel> MenuItems { get; set; }
|
||||
@@ -51,7 +36,5 @@ namespace mbts.ui.controls
|
||||
public ICommand Command { get; set; }
|
||||
|
||||
public object Args { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -489,6 +489,8 @@
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
@@ -503,9 +505,11 @@
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="If the order is at the top of book, the quantity remaining below which the order will be canceled."/>
|
||||
</StackPanel>
|
||||
<uag:QuantityMeasureControl x:Name="PART_TopThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
|
||||
<TextBlock Grid.Row="2">Ticks Off</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" Value="{Binding ticks_off}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.Row="4"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
SelectedItem="{Binding children_vm.child.selected_algo}"
|
||||
|
||||
@@ -72,11 +72,13 @@ namespace mbts.ui.controls {
|
||||
static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = (PopupControl)d;
|
||||
if ((bool)e.NewValue) {
|
||||
target.OnOpened(EventArgs.Empty);
|
||||
target.RaiseEvent(new RoutedEventArgs(OpenedEvent, target));
|
||||
(FocusManager.GetFocusScope(target) as UIElement)?.Focus();
|
||||
Keyboard.Focus(target);
|
||||
target?._popup?.Child?.Focus();
|
||||
} else {
|
||||
target.OnClosed(EventArgs.Empty);
|
||||
target.ReleaseMouseCapture();
|
||||
target.RaiseEvent(new RoutedEventArgs(ClosedEvent, target));
|
||||
if (target.IsKeyboardFocused || target.IsKeyboardFocusWithin)
|
||||
@@ -121,5 +123,8 @@ namespace mbts.ui.controls {
|
||||
_popup.PlacementTarget = PlacementTarget ?? _button;
|
||||
_popup_hook = PopupViewController.attach(_popup, _button);
|
||||
}
|
||||
|
||||
protected virtual void OnOpened(EventArgs e) { }
|
||||
protected virtual void OnClosed(EventArgs e) { }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.extensions;
|
||||
using bts.utils;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utilities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -38,6 +39,32 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Minimum
|
||||
public static readonly DependencyProperty MinimumProperty =
|
||||
WPFHelper.create_dp<uint, TickUpDownUInt>(
|
||||
nameof(Minimum),
|
||||
(o, v) => o.CoerceValue(ValueProperty),
|
||||
uint.MinValue);
|
||||
|
||||
public uint Minimum {
|
||||
get => (uint)GetValue(MinimumProperty);
|
||||
set => SetValue(MinimumProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Maximum
|
||||
public static readonly DependencyProperty MaximumProperty =
|
||||
WPFHelper.create_dp<uint, TickUpDownUInt>(
|
||||
nameof(Maximum),
|
||||
(o, v) => o.CoerceValue(ValueProperty),
|
||||
uint.MaxValue);
|
||||
|
||||
public uint Maximum {
|
||||
get => (uint)GetValue(MaximumProperty);
|
||||
set => SetValue(MaximumProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
WPFHelper.create_dp<uint, TickUpDownUInt>(
|
||||
@@ -53,6 +80,7 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
},
|
||||
(o, v) => MathUtils.within_range(o.Minimum, o.Maximum, v),
|
||||
0u);
|
||||
|
||||
public uint Value {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
@@ -38,30 +39,36 @@ namespace mbts.ui {
|
||||
|
||||
public Lazy<UIElement> Content { get; set; }
|
||||
|
||||
public Action<ShellWindowVM> on_shell_window_vm { get; set; }
|
||||
|
||||
void IShellWindow.on_shell_window_vm(ShellWindowVM shell_window_vm) =>
|
||||
on_shell_window_vm?.Invoke(shell_window_vm);
|
||||
}
|
||||
|
||||
class LazyAnonShellWindow : AnonShellWindow, ISharedWindow {
|
||||
public string thread_id { get; set; }
|
||||
|
||||
public void thread_init() { }
|
||||
}
|
||||
|
||||
public static IShellWindow create(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement, Stream> get_state = null, ImageSource icon = null, string thread_id=null) {
|
||||
public static IShellWindow create(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement, Stream> get_state = null, ImageSource icon = null, string thread_id=null, Action<ShellWindowVM> on_shell_window_vm = null) {
|
||||
if (string.IsNullOrEmpty(thread_id)) {
|
||||
return create<AnonShellWindow>(owner, title, window_type, content, get_state, icon);
|
||||
return create<AnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
|
||||
}
|
||||
else {
|
||||
var res = create<LazyAnonShellWindow>(owner, title, window_type, content, get_state, icon);
|
||||
var res = create<LazyAnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
|
||||
res.thread_id = thread_id;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
static T create<T>(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement,Stream> get_state=null, ImageSource icon=null) where T : AnonShellWindow, new() {
|
||||
static T create<T>(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement,Stream> get_state=null, ImageSource icon=null, Action<ShellWindowVM> on_shell_window_vm = null) where T : AnonShellWindow, new() {
|
||||
var result = new T() {
|
||||
Title = title,
|
||||
window_type = window_type,
|
||||
Icon = icon ?? (owner != null ? owner.icon : Icons.Window),
|
||||
Owner = owner,
|
||||
get_state = get_state,
|
||||
on_shell_window_vm = on_shell_window_vm,
|
||||
Content = content,
|
||||
Disposable = Disposable.Create(() => {
|
||||
if (content.IsValueCreated) {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using bts;
|
||||
using mbts.ui.native;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using bts;
|
||||
using mbts.ui.native;
|
||||
|
||||
namespace mbts.ui {
|
||||
public partial class WindowStyle : ResourceDictionary {
|
||||
// 'Top' is 0 because the window's TitleContent overlaps the top-resize-border.
|
||||
public static readonly Thickness NegativeResizeBorderThickness = new Thickness(-5, 0, -5, -5);
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler
|
||||
CloseWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Close(), "closing"),
|
||||
HideWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Visibility = Visibility.Hidden, "hiding"),
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace mbts.ui.shell {
|
||||
|
||||
}
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
}
|
||||
|
||||
@@ -28,16 +28,27 @@ namespace mbts.ui.shell {
|
||||
model.Dispose();
|
||||
}
|
||||
|
||||
[Export("username")]
|
||||
[Export(Exports.UserName)]
|
||||
public string UserName { get; private set; }
|
||||
|
||||
[Export("userid")]
|
||||
[Export(Exports.UserID)]
|
||||
public uint UserId { get; private set; }
|
||||
|
||||
[Export]
|
||||
public UserTuple UserTuple => new UserTuple(UserId, UserName);
|
||||
|
||||
[Export]
|
||||
public AuthTokenImmutable Token { get; private set; }
|
||||
|
||||
[Export("workspace")]
|
||||
public string Workspace { get; private set; }
|
||||
|
||||
public static class Exports {
|
||||
public const string
|
||||
UserName = "username",
|
||||
UserID = "userid",
|
||||
UserTuple = "usertuple";
|
||||
}
|
||||
}
|
||||
|
||||
public struct User {
|
||||
@@ -49,8 +60,7 @@ namespace mbts.ui.shell {
|
||||
public readonly UInt32 id;
|
||||
}
|
||||
|
||||
public class LoginModel : IDisposable
|
||||
{
|
||||
public class LoginModel : IDisposable {
|
||||
// Singleton to help bootstrap the system
|
||||
private static LoginModel _instance = new LoginModel();
|
||||
public static LoginModel instance { get { return _instance; } }
|
||||
@@ -70,6 +80,11 @@ namespace mbts.ui.shell {
|
||||
public bool HasFatalError { get; set; }
|
||||
public User[] Users { get; private set; }
|
||||
|
||||
public bool try_get_user(string name, out User user) =>
|
||||
Users.TryGetFirst(u => u.name == name, out user);
|
||||
|
||||
public bool user_exists(string name) =>
|
||||
Users.Any(u => u.name == name);
|
||||
|
||||
// The final username
|
||||
public (string, uint, AuthTokenImmutable) get_user() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
xmlns:uicore="clr-namespace:mbts.ui"
|
||||
xmlns:converters="clr-namespace:mbts.ui.converters"
|
||||
xmlns:autocomplete="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete"
|
||||
mc:Ignorable="d" MouseDown="Window_MouseDown"
|
||||
Width="500" Height="309" WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen"
|
||||
ui:StateManager.VisualState="{Binding VisualState}">
|
||||
@@ -82,7 +83,13 @@
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ComboBox ItemsSource="{Binding UserNames}" Grid.ColumnSpan="2" Margin="10" SelectedItem="{Binding Name, Mode=TwoWay}" Width="160" />
|
||||
<TextBox
|
||||
Grid.ColumnSpan="2"
|
||||
autocomplete:AutoCompleteBehavior.AutoCompleteItemsSource="{Binding KnownUsers}"
|
||||
Margin="10"
|
||||
Width="160"
|
||||
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
<ComboBox ItemsSource="{Binding Workspaces}" Grid.Row="1" Grid.ColumnSpan="2" Margin="10" SelectedItem="{Binding SelectedWorkspace, Mode=TwoWay}" Width="160" MaxWidth="160" />
|
||||
|
||||
<PasswordBox Grid.Row="2" Margin="10" MinWidth="120" Grid.ColumnSpan="2" x:Name="_password" PasswordChanged="_passwordChanged" Style="{DynamicResource PasswordBoxStyle1}" Width="160">
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace mbts.ui.shell
|
||||
_dispatcher = disp;
|
||||
_model = model;
|
||||
Workspaces = new ObservableCollection<string>();
|
||||
LoginCommand = new DelegateCommand(OnLogin, _ => UserNames.Count > 0 && SelectedWorkspace != null);
|
||||
LoginCommand = new DelegateCommand(OnLogin, _ => !string.IsNullOrEmpty(_name) && SelectedWorkspace != null);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
EmailCommand = new DelegateCommand(OnSendMail);
|
||||
if (_model.HasFatalError) {
|
||||
@@ -42,9 +42,10 @@ namespace mbts.ui.shell
|
||||
void _model_Ready() {
|
||||
_dispatcher.BeginInvoke(() => {
|
||||
if (!_model.HasFatalError) {
|
||||
this.UserNames.AddRange(_model.Users.Select(u => u.name));
|
||||
this.VisualState = "LoginState";
|
||||
load_username();
|
||||
if (!string.IsNullOrEmpty(_name))
|
||||
KnownUsers.Add(_name);
|
||||
LoginCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
else {
|
||||
@@ -57,8 +58,8 @@ namespace mbts.ui.shell
|
||||
private string _vstate = "Normal";
|
||||
public string VisualState { get { return _vstate; } set { _vstate = value; RaisePropertyChanged("VisualState"); } }
|
||||
|
||||
private ObservableCollection<string> _user_names = new ObservableCollection<string>();
|
||||
public ObservableCollection<string> UserNames { get { return _user_names; } }
|
||||
/// <see cref="KnownUsers"/> should only contain the user-name of the last login (if none exists, it should be empty).
|
||||
public ObservableCollection<string> KnownUsers { get; } = new ObservableCollection<string>();
|
||||
private string _name;
|
||||
public string Name {
|
||||
get { return _name; }
|
||||
@@ -82,12 +83,13 @@ namespace mbts.ui.shell
|
||||
private async void update_workspaces(string name) {
|
||||
Workspaces.Clear();
|
||||
Workspaces.Add("New workspace...");
|
||||
var user = _model.Users.First(u => u.name == name);
|
||||
var to_add = await _model.workspaces_for(user);
|
||||
this.SelectedWorkspace = Workspaces.First();
|
||||
if (to_add != null && name == _name && Workspaces.Count == 1) {
|
||||
Workspaces.AddRange(to_add);
|
||||
load_last_workspace(name);
|
||||
SelectedWorkspace = Workspaces.First();
|
||||
if (_model.try_get_user(name, out var user)) {
|
||||
var to_add = await _model.workspaces_for(user);
|
||||
if (to_add != null && name == _name && Workspaces.Count == 1) {
|
||||
Workspaces.AddRange(to_add);
|
||||
load_last_workspace(name);
|
||||
}
|
||||
}
|
||||
RaisePropertyChanged("SelectedWorkspace");
|
||||
LoginCommand.RaiseCanExecuteChanged();
|
||||
@@ -111,7 +113,7 @@ namespace mbts.ui.shell
|
||||
var value = key.GetValue("login");
|
||||
if (value != null) {
|
||||
var n = value.ToString();
|
||||
if (UserNames.Contains(n)) {
|
||||
if (_model.user_exists(n)) {
|
||||
Name = n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,20 @@
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Style="{StaticResource BTSWindow}"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="{Binding}"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}"
|
||||
UseLayoutRounding="True"
|
||||
>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Margin="-2,-2,0,1">
|
||||
<Image HorizontalAlignment="Left" VerticalAlignment="Stretch" Height="17" Margin="0,-2,6,0" Source="{x:Static u:Icons.Admin}"/>
|
||||
<TextBlock Margin="0,-1,0,0" Text="{Binding title, StringFormat='{}{0} Settings'}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Resources>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
@@ -346,13 +356,16 @@
|
||||
</c:PopupControl>
|
||||
<c:ContractPickerPopup
|
||||
x:Name="security_picker"
|
||||
AllowEmpty="True"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
IsEnabled="{Binding selected_profile.is_global}"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource {x:Static usp:ShellSettingsDialog.SecurityMapKey}}"
|
||||
SelectedCriteria="{Binding selected_profile.security_criteria}"
|
||||
SelectedCriteria="{Binding selected_profile.security_criteria, UpdateSourceTrigger=Explicit}"
|
||||
ShowDialogSkirt="True"
|
||||
Style="{StaticResource SparkToolBarItem}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
|
||||
>
|
||||
<c:ContractPickerPopup.Header>
|
||||
<Image Source="{x:Static u:Icons.HandPointingOverDocument}"/>
|
||||
@@ -376,7 +389,7 @@
|
||||
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
|
||||
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" VerticalAlignment="Center" ItemsSource="{Binding action_menu_items}"/>
|
||||
</ToolBar>
|
||||
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
|
||||
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Left" Width="758" MinHeight="234" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
@@ -417,23 +430,26 @@
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid Margin="{x:Static u:WindowStyle.NegativeResizeBorderThickness}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
VerticalAlignment="Top"
|
||||
Content="{Binding instance_vm}"
|
||||
ContentTemplateSelector="{StaticResource GridSettingsTemplateSelector}"
|
||||
/>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
<ContentControl
|
||||
VerticalAlignment="Top"
|
||||
Content="{Binding instance_vm}"
|
||||
ContentTemplateSelector="{StaticResource GridSettingsTemplateSelector}"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
<c:DialogSkirt
|
||||
Grid.Row="1"
|
||||
BorderThickness="1"
|
||||
BorderBrush="DarkGray"
|
||||
LeftItems="{Binding left_skirt_items}"
|
||||
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
|
||||
RightItems="{Binding right_skirt_items}"
|
||||
BorderBrush="DarkGray"
|
||||
BorderThickness="0,1,0,0"
|
||||
Margin="-1,0,-1,-1"
|
||||
>
|
||||
<c:DialogSkirt.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
|
||||
@@ -23,11 +23,12 @@ namespace mbts.spark {
|
||||
T get_result();
|
||||
}
|
||||
|
||||
class ShellSettingsDialogVM<T, TVM> : ViewModelBase, IRequestViewClose, IDisposable where T : class, IEquatable<T> where TVM : IWindowSettingsVM<T> {
|
||||
class ShellSettingsDialogVM<T, TVM> : ViewModelBase, IRequestViewClose, IDisposable, IWillLetYouKnowWhenIDispose where T : class, IEquatable<T> where TVM : IWindowSettingsVM<T> {
|
||||
readonly Property<bool> _use_default;
|
||||
readonly DialogSkirtItem _ok_item, _apply_item, _cancel_item, _apply_to_defaults_item;
|
||||
readonly DelegateProperty<IEnumerable<DialogSkirtItem>> _left_skirt_items;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>(2); // initialize with 2 capacity bc the only potential disposables are the instance/default vms
|
||||
string _title;
|
||||
|
||||
public ShellSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null, bool show_save_as_defaults = true) {
|
||||
if (merge_with_current == null)
|
||||
@@ -94,7 +95,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
public event Action<bool> OnDisposed;
|
||||
|
||||
public string title { get => _title; set => Set(ref _title, value); }
|
||||
public TVM instance_vm { get; }
|
||||
public TVM default_vm { get; }
|
||||
public IReadOnlyCollection<IMenuItemVM> action_menu_items { get; }
|
||||
@@ -111,8 +114,10 @@ namespace mbts.spark {
|
||||
set => _use_default.Value = value;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
public void Dispose() {
|
||||
Disposable.dispose(_resources);
|
||||
OnDisposed?.Invoke(true);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfiledShellSettingsDialogVM<T, TVM, TProfile> : DisposableViewModelBase, IRequestViewClose where T : class, IEquatable<T> where TVM : class, IValueFactory<T>, IObservable<T> where TProfile : ISettingsProfile<T> {
|
||||
@@ -120,6 +125,7 @@ namespace mbts.spark {
|
||||
readonly Property<ProfileVM> _selected_profile;
|
||||
readonly Func<T, TVM> _vm_factory;
|
||||
readonly ProfileVM _local_profile;
|
||||
string _title;
|
||||
bool _is_copy_menu_open;
|
||||
DialogSkirtItem _apply_item, _ok_item, _cancel_item;
|
||||
MenuItemVM _import_item, _export_item, _load_factory_item;
|
||||
@@ -271,6 +277,7 @@ namespace mbts.spark {
|
||||
public event Action<SettingsProfile<T>> applied;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public string title { get => _title; set => Set(ref _title, value); }
|
||||
public ObservableCollection<ProfileVM> profiles { get; } = new ObservableCollection<ProfileVM>();
|
||||
public ProfileVM selected_profile {
|
||||
get => _selected_profile.Value;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using mbts.ui.native;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using mbts.ui.native;
|
||||
using mbts.ui.utility;
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
[Export(typeof(ILauncher))]
|
||||
@@ -23,7 +23,7 @@ namespace mbts.ui.shell {
|
||||
[ImportingConstructor]
|
||||
public LauncherVM(
|
||||
ObservableCollection<IShellModule> modules,
|
||||
[Import("username")]string username,
|
||||
[Import(LoginContainer.Exports.UserName)]string username,
|
||||
ErrorReporter errors, IBus bus) {
|
||||
_user = username;
|
||||
_modules = new ObservableAutoWrapper<ShellModuleVM, IShellModule>(modules, m => new ShellModuleVM(m, bus));
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
using System;
|
||||
using mbts.ui;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Input;
|
||||
using System.IO;
|
||||
using System.Windows.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using mbts.ui.settings;
|
||||
using System.Windows.Interop;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using bts;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
public class ShellWindowVM : ViewModelBase, IDisposable
|
||||
{
|
||||
namespace mbts.ui.shell {
|
||||
public class ShellWindowVM : ViewModelBase, IDisposable {
|
||||
IShellWindow _model;
|
||||
UIElement _ui;
|
||||
IWindowManager _wm;
|
||||
Dispatcher _dispatcher;
|
||||
HwndContentHost _hwnd_host;
|
||||
|
||||
|
||||
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
|
||||
Log.assert(model != null);
|
||||
_model = model;
|
||||
@@ -44,6 +33,7 @@ namespace mbts.ui.shell
|
||||
}
|
||||
|
||||
_edit_mode = backing(nameof(EditMode), false);
|
||||
model.on_shell_window_vm(this);
|
||||
}
|
||||
|
||||
void title_changed() {
|
||||
@@ -56,6 +46,8 @@ namespace mbts.ui.shell
|
||||
RaisePropertyChanged("Icon");
|
||||
}
|
||||
|
||||
public event Action<string> TitleOverrideChanged;
|
||||
|
||||
public string host_id { get; set; }
|
||||
public Dispatcher Dispatcher { get { return _dispatcher; } }
|
||||
|
||||
@@ -73,6 +65,7 @@ namespace mbts.ui.shell
|
||||
}
|
||||
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
|
||||
_edit_mode.set(false);
|
||||
TitleOverrideChanged?.Invoke(_title_override);
|
||||
}
|
||||
}
|
||||
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
|
||||
@@ -218,7 +211,6 @@ namespace mbts.ui.shell
|
||||
public void Dispose() {
|
||||
_model.TitleChanged -= title_changed;
|
||||
_model.IconChanged -= icon_changed;
|
||||
|
||||
if (IsShared) {
|
||||
// Shared threads continue running
|
||||
_dispatcher.Invoke(() => Disposable.dispose(ref _model));
|
||||
@@ -237,5 +229,4 @@ namespace mbts.ui.shell
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,12 +45,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Margin="4" Text="{Binding SaveName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
|
||||
<CheckBox Margin="4,0,4,0" Grid.Row="1" IsChecked="{Binding IsPublicWorkspace}" HorizontalAlignment="Left">Shared</CheckBox>
|
||||
<Button Grid.Column="1" HorizontalAlignment="Right" Margin="4" Command="{Binding SaveWorkspace}" IsDefault="True" MinWidth="60">Save</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
@@ -13,10 +14,14 @@ using System.Windows.Input;
|
||||
using static mbts.spark.SparkTags;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class ContractPickerModel : ViewModelBase, IDisposable {
|
||||
internal class ContractPickerModel : ViewModelBase, IEditableObject, IDisposable {
|
||||
private const string dot = "•";
|
||||
bool _is_edit_mode;
|
||||
|
||||
readonly IRaiseCanExecuteChangedCommand _add_criteria_command, _remove_criteria_command, _ok_button_command;
|
||||
|
||||
bool _allow_empty, _is_edit_mode;
|
||||
SecurityImmutable? _pending_sec;
|
||||
(SecurityCriteria criteria, SecurityImmutable? sec, SecurityMD? md)? _snapshot;
|
||||
|
||||
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null)
|
||||
: this(bus.subscribe(SecuritiesTag), edit_mode, existing_criteria, world_view_filter) { }
|
||||
@@ -31,7 +36,7 @@ namespace mbts.spark {
|
||||
(securities_view as ListCollectionView).CustomSort = new SecuritySorter();
|
||||
_subs.Add(source.OnDispatcher().Subscribe(on_securities_changed));
|
||||
|
||||
add_criteria_command = new DelegateCommand(
|
||||
_add_criteria_command = new DelegateCommand(
|
||||
execute: _ => {
|
||||
if (is_edit_mode && selected_securities.Count > 1) {
|
||||
// make separate entries in case multiple securities are selected
|
||||
@@ -65,18 +70,20 @@ namespace mbts.spark {
|
||||
can_execute: _ => { return !String.IsNullOrEmpty(criteria_input_name); }
|
||||
);
|
||||
|
||||
remove_criteria_command = new DelegateCommand(
|
||||
_remove_criteria_command = new DelegateCommand(
|
||||
execute: _ => {
|
||||
security_criteria_clauses.removeRange(selected_security_criteria_clauses);
|
||||
ok_button_command.TryRaiseCanExecuteChanged();
|
||||
remove_criteria_command.TryRaiseCanExecuteChanged();
|
||||
_remove_criteria_command.TryRaiseCanExecuteChanged();
|
||||
},
|
||||
can_execute: _ => { return security_criteria_clauses.Count > 0; }
|
||||
);
|
||||
|
||||
ok_button_command = new DelegateCommand(
|
||||
_ok_button_command = new DelegateCommand(
|
||||
execute: _ => { ok_action?.Invoke(); },
|
||||
can_execute: _ => {
|
||||
if (_allow_empty)
|
||||
return true;
|
||||
if (!is_edit_mode) {
|
||||
return (selected_securities.Count > 0);
|
||||
} else {
|
||||
@@ -236,9 +243,9 @@ namespace mbts.spark {
|
||||
public List<uint> selected_securities = new List<uint>();
|
||||
public ObservableCollection<SecurityImmutable> securities { get; } = new ObservableCollection<SecurityImmutable>();
|
||||
|
||||
public ICommand add_criteria_command { get; }
|
||||
public ICommand remove_criteria_command { get; }
|
||||
public ICommand ok_button_command { get; }
|
||||
public ICommand add_criteria_command => _add_criteria_command;
|
||||
public ICommand remove_criteria_command => _remove_criteria_command;
|
||||
public ICommand ok_button_command => _ok_button_command;
|
||||
public ICommand cancel_button_command { get; }
|
||||
public ICommand add_symbol_command { get; }
|
||||
public ICommand add_security_type_command { get; }
|
||||
@@ -266,9 +273,18 @@ namespace mbts.spark {
|
||||
|
||||
private SecurityCriteria _world_view_filter;
|
||||
|
||||
public Action ok_action { get; set; }
|
||||
public Action cancel_action { get; set; }
|
||||
public event Action ok_action;
|
||||
public event Action cancel_action;
|
||||
|
||||
public bool allow_empty {
|
||||
get => _allow_empty;
|
||||
set {
|
||||
if (_allow_empty != value) {
|
||||
_allow_empty = value;
|
||||
_ok_button_command.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool is_edit_mode { get => _is_edit_mode; set => Set(ref _is_edit_mode, value); }
|
||||
|
||||
public event EventHandler<SecurityCriteriaClauseChangedEventsArgs> security_criteria_clause_changed;
|
||||
@@ -319,6 +335,25 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
#region IEditableObject
|
||||
public void BeginEdit() => _snapshot = (result_criteria, _pending_sec ?? selected_security, selected_security_md);
|
||||
public void EndEdit() => _snapshot = null;
|
||||
public void CancelEdit() {
|
||||
if (_snapshot.HasValue) {
|
||||
var (criteria, sec, md) = _snapshot.Value;
|
||||
security_criteria_clauses.SetItems(criteria.clauses);
|
||||
selected_security = sec;
|
||||
/// A <see cref="_pending_sec"/> may have been used to create the snapshot when <see cref="BeginEdit"/> was called (this happens when securities are loaded lazily).
|
||||
/// If this is the case, pending sec was assigned to the <see cref="_snapshot"/> security;
|
||||
/// check if the security is now present in <see cref="all_securities"/>, if so, use that value instead of the null.
|
||||
selected_security_md = sec.HasValue && !md.HasValue && all_securities.TryGetValue(sec.Value.Id, out var md_match) ? md_match : md;
|
||||
_snapshot = null;
|
||||
_add_criteria_command.RaiseCanExecuteChanged();
|
||||
_remove_criteria_command.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void _update_exchanges() {
|
||||
all_exchanges.sync_with(all_securities
|
||||
.Select(s => s.Value.security.Exchange)
|
||||
@@ -389,7 +424,6 @@ namespace mbts.spark {
|
||||
public void Dispose() {
|
||||
Disposable.dispose(_subs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class SecurityCriteriaClauseChangedEventsArgs : EventArgs
|
||||
|
||||
@@ -14,9 +14,13 @@ namespace mbts.ui.controls {
|
||||
/// Interaction logic for ContractPickerView.xaml
|
||||
/// </summary>
|
||||
partial class ContractPickerPopup : PopupControl {
|
||||
public static readonly EventHandler
|
||||
FlushSelectedCriteriaHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedCriteriaProperty)?.UpdateSource(),
|
||||
FlushSelectedSecurityHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedSecurityProperty)?.UpdateSource();
|
||||
|
||||
IReadOnlyDictionary<uint, SecurityMD> _cached_security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
ContractPickerPopupController _controller;
|
||||
bool _is_lazy;
|
||||
bool _allow_empty, _is_lazy;
|
||||
|
||||
public ContractPickerPopup() {
|
||||
SetCurrentValue(SecurityMapProperty, new Dictionary<uint, SecurityMD>(0));
|
||||
@@ -57,12 +61,14 @@ namespace mbts.ui.controls {
|
||||
void on_data_context_disposed(bool obj) => Disposable.dispose(ref _controller);
|
||||
}
|
||||
|
||||
public event EventHandler OKClick, CancelClick;
|
||||
|
||||
event Action<IReadOnlyDictionary<uint, SecurityMD>> _security_map_changed;
|
||||
event Action<SecurityCriteria> _selected_criteria_changed;
|
||||
event Action<bool> _is_lazy_changed;
|
||||
event Action<bool> _allow_empty_changed, _is_lazy_changed;
|
||||
|
||||
public static IValueConverter SecurityCriteriaTextConverter =
|
||||
ValueConverter.create<SecurityCriteria, string>(s => s == null ? "(None)" : string.Join(", ", s.clauses.empty_if_null()));
|
||||
ValueConverter.create<SecurityCriteria, string>(s => s.is_defined() ? string.Join(", ", s.clauses.empty_if_null()) : "(None)");
|
||||
|
||||
#region ShowDialogSkirt
|
||||
public static readonly DependencyProperty ShowDialogSkirtProperty =
|
||||
@@ -134,13 +140,9 @@ namespace mbts.ui.controls {
|
||||
public static readonly DependencyProperty CloseOnOKProperty =
|
||||
WPFHelper.create_dp<bool, ContractPickerPopup>(
|
||||
nameof(CloseOnOK),
|
||||
(o, v) => {
|
||||
o.Controller.model.ok_action = v ? new Action(() => {
|
||||
o.set_current_if_changed(SelectedSecurityMDProperty, o.Controller.model.selected_security_md);
|
||||
o.set_current_if_changed(SelectedSecurityProperty, o.Controller.model.selected_security);
|
||||
o.set_current_if_changed(SelectedCriteriaProperty, o.Controller.model.result_criteria);
|
||||
o.SetCurrentValue(IsOpenProperty, false);
|
||||
}) : null;
|
||||
(o, oldval, newval) => {
|
||||
if (oldval) o.Controller.model.ok_action -= o._close;
|
||||
if (newval) o.Controller.model.ok_action += o._close;
|
||||
});
|
||||
|
||||
public bool CloseOnOK {
|
||||
@@ -153,7 +155,10 @@ namespace mbts.ui.controls {
|
||||
public static readonly DependencyProperty CloseOnCancelProperty =
|
||||
WPFHelper.create_dp<bool, ContractPickerPopup>(
|
||||
nameof(CloseOnCancel),
|
||||
(o, v) => o.Controller.model.cancel_action = v ? new Action(() => o.SetCurrentValue(IsOpenProperty, false)) : null);
|
||||
(o, oldval, newval) => {
|
||||
if (oldval) o.Controller.model.cancel_action -= o._close;
|
||||
if (newval) o.Controller.model.cancel_action += o._close;
|
||||
});
|
||||
|
||||
public bool CloseOnCancel {
|
||||
get => (bool)GetValue(CloseOnCancelProperty);
|
||||
@@ -168,6 +173,16 @@ namespace mbts.ui.controls {
|
||||
set => _controller.model.is_edit_mode = value;
|
||||
}
|
||||
|
||||
public bool AllowEmpty {
|
||||
get => _allow_empty;
|
||||
set {
|
||||
if (_allow_empty != value) {
|
||||
_allow_empty = value;
|
||||
_allow_empty_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLazy {
|
||||
get => _is_lazy;
|
||||
set {
|
||||
@@ -178,12 +193,28 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e) {
|
||||
if (ShowDialogSkirt)
|
||||
_controller.model.CancelEdit();
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e) {
|
||||
if (ShowDialogSkirt)
|
||||
_controller.model.BeginEdit();
|
||||
}
|
||||
|
||||
void _close() => SetCurrentValue(IsOpenProperty, false);
|
||||
|
||||
public class ContractPickerPopupController : DisposableViewModelBase {
|
||||
readonly Property<SecurityCriteria> _selected_criteria;
|
||||
|
||||
public ContractPickerPopupController(ContractPickerPopup owner, ContractPickerModel model) {
|
||||
_selected_criteria = backing<SecurityCriteria>(nameof(selected_criteria));
|
||||
model.allow_empty = owner._allow_empty;
|
||||
model.security_criteria_clauses.CollectionChanged += on_clauses_collection_changed;
|
||||
model.ok_action += on_ok;
|
||||
model.cancel_action += on_cancel;
|
||||
owner._allow_empty_changed += on_allow_empty_changed;
|
||||
owner._selected_criteria_changed += on_selected_criteria_changed;
|
||||
_dispose = this.model = model;
|
||||
// local methods
|
||||
@@ -203,12 +234,28 @@ namespace mbts.ui.controls {
|
||||
_selected_criteria.set(criteria ?? new SecurityCriteria());
|
||||
}
|
||||
|
||||
void on_allow_empty_changed(bool value) => model.allow_empty = value;
|
||||
void on_clauses_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
owner._selected_criteria_changed -= on_selected_criteria_changed;
|
||||
_selected_criteria.set(model.result_criteria);
|
||||
WPFHelper.set_current_if_changed(owner, SelectedCriteriaProperty, selected_criteria);
|
||||
owner._selected_criteria_changed += on_selected_criteria_changed;
|
||||
}
|
||||
void on_ok() {
|
||||
model.EndEdit();
|
||||
propagate_to_owner();
|
||||
owner.OKClick?.Invoke(owner, EventArgs.Empty);
|
||||
}
|
||||
void on_cancel() {
|
||||
model.CancelEdit();
|
||||
propagate_to_owner();
|
||||
owner.CancelClick?.Invoke(owner, EventArgs.Empty);
|
||||
}
|
||||
void propagate_to_owner() {
|
||||
owner.set_current_if_changed(SelectedSecurityMDProperty, owner.Controller.model.selected_security_md);
|
||||
owner.set_current_if_changed(SelectedSecurityProperty, owner.Controller.model.selected_security);
|
||||
owner.set_current_if_changed(SelectedCriteriaProperty, owner.Controller.model.result_criteria);
|
||||
}
|
||||
}
|
||||
|
||||
public ContractPickerModel model { get; }
|
||||
|
||||
@@ -104,6 +104,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
#region Extensions
|
||||
public static IObservable<SecurityMD?> to_security_md(this IObservable<SecurityImmutable?> security_source, IBus bus) =>
|
||||
bus.subscribe(SparkTags.SecuritiesTag).CombineLatest(
|
||||
security_source, (data, sec) => sec.HasValue && data.TryGetValue(sec.Value.Id, out var smd) ?smd : (SecurityMD?)null);
|
||||
|
||||
public static IObservable<SecurityImmutable> to_security(IObservable<uint> security_key, IBus bus) =>
|
||||
security_key.CombineLatest(bus.subscribe(SparkTags.SecuritiesTag), (sk, map) => map.GetValueOrDefault(sk).security);
|
||||
|
||||
|
||||
Loaded 100 of 243 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user