Compare commits
2
Commits
mview-popups
..
fix2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0140aadaf | ||
|
|
35a69f42bd |
No files matched your search
@@ -154,7 +154,7 @@ insert_anchor_links = "right"
|
||||
|
||||
- Pay Less - If enabled by checking the checkbox, the spreader will
|
||||
attempt to pay less when the quantity at the level of the calculated
|
||||
hedge price is less than the threshold defined. The hedge order is
|
||||
hedge price is more than the threshold defined. The hedge order is
|
||||
paid less by the “Payless Ticks” as defined per leg.
|
||||
- Pay Up - If enabled by checking the checkbox, the spreader will pay
|
||||
up when the quantity at the level of the calculated hedge price is
|
||||
|
||||
@@ -4,36 +4,17 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.12.0
|
||||
### User Visible Changes
|
||||
- Added support for ICE market data and order execution
|
||||
### Internal Changes
|
||||
- Lots of permission / admin changes to facilitate ICE
|
||||
|
||||
## 2.11.0
|
||||
### User Visible Changes
|
||||
- Fixed fills form time filter to also apply to SOD fills
|
||||
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
|
||||
- Removed the option to enter manual spread fills
|
||||
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
|
||||
### Internal Changes
|
||||
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
|
||||
- To facilitate support, allowed the bts user to view and edit algos from all other users
|
||||
- Improved system tests
|
||||
- Improved log messages in UI to include dispatcher thread name
|
||||
- Added support for Windows core dumps in the UI
|
||||
|
||||
## 2.10.0
|
||||
### User Visible Changes
|
||||
- Fills now include information about the algo that originated the fill
|
||||
- A new, more powerful PnL form is now available
|
||||
### API Changes
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
### Bug Fixes
|
||||
- Brought back the "select all" check box in the mini order book
|
||||
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
|
||||
### Internal Changes
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
## 2.9.3
|
||||
### Internal Changes
|
||||
- Fixed system test scripts
|
||||
@@ -49,7 +30,7 @@ insert_anchor_links = "right"
|
||||
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
|
||||
### Api
|
||||
- C# API sample now uses synchronous calls.
|
||||
|
||||
|
||||
## 2.9.0
|
||||
### User Visible Changes
|
||||
- Improved Workspace management User Interface
|
||||
@@ -57,11 +38,11 @@ insert_anchor_links = "right"
|
||||
- The Spark UI Window group now has a title in the Windows task bar
|
||||
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
|
||||
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
|
||||
- Added import facilities for GMI position files
|
||||
- Added import facilities for GMI position files
|
||||
- Added the ability for risk managers to enter manual fills
|
||||
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
|
||||
### Bug Fixes
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the method used to determine if a security is expired to take into account the start of day time
|
||||
### API Changes
|
||||
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
|
||||
@@ -98,10 +79,10 @@ insert_anchor_links = "right"
|
||||
- Made it easier to generate simple UI's for new order types
|
||||
### API Changes
|
||||
- Dynamic orders can now use QuantityMeasure parameter types
|
||||
|
||||
|
||||
## 2.7.2
|
||||
### User Visible Changes
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Added new Splitter algo. From the manual: The splitter is useful
|
||||
for pro-rata markets where you want to vary your exposure
|
||||
independent of the order quantity that you want to fill. The
|
||||
@@ -109,13 +90,13 @@ insert_anchor_links = "right"
|
||||
the order quantity but it will put exposed_qty total size on the
|
||||
market. Splitter favors order size over queue position.
|
||||
### Internal Changes
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
## 2.7.1
|
||||
### User Visible Changes
|
||||
- Fixed some issues with the Cover algo, where in some circumstances
|
||||
it would erroneously cancel launched hedges on completion.
|
||||
|
||||
|
||||
## 2.7.0
|
||||
### User Visible Changes
|
||||
- Allowed algos to process "in-flight" fills while shutting down
|
||||
@@ -134,25 +115,25 @@ insert_anchor_links = "right"
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
|
||||
- Fixed a rare race condition that could lead to erroneous limit enforcement
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
|
||||
## 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, 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
|
||||
- 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
|
||||
@@ -182,13 +163,13 @@ insert_anchor_links = "right"
|
||||
- Made Cover order more independent from its hedging chidren:
|
||||
- Cover continues to operate even if an hedging child is canceled
|
||||
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed manual fills to appear in day PnL rather than in the SOD records
|
||||
- Add buy/sell coloring to fill rows in fills window
|
||||
- Streamlined the mini order book:
|
||||
- Display of the mini order book popup is quicker
|
||||
- Only exchange orders are displayed
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
|
||||
- A checkbox column has been added to make clearer which rows are selected
|
||||
### API Changes
|
||||
@@ -198,7 +179,7 @@ insert_anchor_links = "right"
|
||||
- Added system requirements to Spark documentation
|
||||
- Implemented keycodes
|
||||
- Temporarily rejected stop and stop limit orders, pending bug fixes
|
||||
|
||||
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
@@ -209,30 +190,30 @@ insert_anchor_links = "right"
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
- Added security symbol column to fills window
|
||||
- Re-organized ladder settings dialog to make it clearer and easier to use
|
||||
|
||||
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
- Corrected handling of stop limit orders
|
||||
|
||||
|
||||
## 2.1.0
|
||||
### User Visible Changes
|
||||
- Added support for the GLBX Iceberg order type
|
||||
@@ -243,7 +224,7 @@ insert_anchor_links = "right"
|
||||
- Added PIQ to the mini order book
|
||||
- Fixed a rare bug that could lead to server failure when requesting market data
|
||||
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
|
||||
|
||||
|
||||
## 2.0.5
|
||||
### User Visible Changes
|
||||
- Fixed a bug in the handling of rejected QHO orders
|
||||
@@ -253,19 +234,19 @@ insert_anchor_links = "right"
|
||||
## 2.0.4
|
||||
### User Visible Changes
|
||||
- Fixed performance regression in order manager
|
||||
|
||||
|
||||
### Internal changes
|
||||
- Improved market data diagnostics for bad configurations
|
||||
- Changed market data default buffer size
|
||||
|
||||
|
||||
## 2.0.3
|
||||
### Internal changes
|
||||
- Improved backwards compatibility in UI settings
|
||||
|
||||
|
||||
|
||||
## 2.0.2
|
||||
### User Visible Changes
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the bid or ask column in the ladder no longer sends an order
|
||||
|
||||
### Internal changes
|
||||
@@ -293,8 +274,8 @@ insert_anchor_links = "right"
|
||||
- Improved settings manager tool output
|
||||
- Added diagnostics in the event MD memory corruption is detected
|
||||
- Added systems tests for join,lean, iceberg and spreader orders
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
## 1.6.2
|
||||
### User Visible Changes
|
||||
### Exchange simulator
|
||||
@@ -333,21 +314,21 @@ insert_anchor_links = "right"
|
||||
- These columns can be restored by toggling their visibility within the PnL's Settings form
|
||||
- A new Traded Today column has been added to the PnL
|
||||
- Filtering on this column will allow a user to restrict the report to only show today's position
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
- Added logging to help troubleshoot shell exit hang
|
||||
|
||||
## 1.5.6
|
||||
### User Visible Changes
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Improved algo documentation
|
||||
- Changed how the spreader uses net change
|
||||
- Simplified trigger algo user interface
|
||||
### Internal changes
|
||||
### Internal changes
|
||||
- Defaulted to not recording sim market data
|
||||
- Improved documentation build
|
||||
- Improved documentation build
|
||||
- Added additional performance tracing
|
||||
|
||||
|
||||
## 1.5.5
|
||||
- Fixed Market View 'stale working orders' bug
|
||||
|
||||
@@ -355,11 +336,11 @@ insert_anchor_links = "right"
|
||||
- Avoided logging when warming up cache
|
||||
|
||||
## 1.5.3
|
||||
- Fixed sounds startup issue
|
||||
- Fixed sounds startup issue
|
||||
|
||||
## 1.5.2
|
||||
- Added better logging to AlgoDefinitionService
|
||||
|
||||
|
||||
## 1.5.1
|
||||
- Fixed missing securities in the Fills and P&L windows.
|
||||
|
||||
@@ -380,7 +361,7 @@ insert_anchor_links = "right"
|
||||
#### Bug Fixes
|
||||
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
|
||||
- Fixed a bug in the order book where sometimes parent orders would not update their last message column.
|
||||
|
||||
|
||||
#### Algos
|
||||
- Added OCO Algo
|
||||
- An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill. Composing this order with Cover simulates scalping functionality
|
||||
@@ -388,7 +369,7 @@ insert_anchor_links = "right"
|
||||
- Improved SDK documentation and usability
|
||||
- Dynamic orders are now properly identified in the order book
|
||||
#### Documentation
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- The documentation has been updated and reorganized to make information easier to find
|
||||
### Internal Changes
|
||||
- Improved robustness of limit system test
|
||||
|
||||
+1
-1
Submodule docker/linux updated: c40e6eba4b...e13bb5d8df.
+1
-1
Submodule externals updated: 6d8f64ca5c...b5ca264286.
@@ -1,74 +1,83 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
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 {
|
||||
public readonly UserTuple user;
|
||||
|
||||
IDisposable _dispose;
|
||||
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
IReadOnlyDictionary<uint, string>
|
||||
_account_map = new Dictionary<uint, string>(),
|
||||
_user_map = new Dictionary<uint, string>();
|
||||
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, UserTuple user) {
|
||||
AdminModel(AdminService service, [Import]UserTuple user) {
|
||||
this.user = user;
|
||||
// accounts
|
||||
on_accounts = service.subscribei_admin_data()
|
||||
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
|
||||
usr.UserData.AccountPermissions.AccountPermissions
|
||||
.Where(kv => (kv.Value.TradingPermission != null && kv.Value.TradingPermission.HasPermissions) || kv.Value.AdminPermission.HasPermissions)
|
||||
.Select(kv => ad.Accounts.Where(a => a.Id == kv.Key).TryGetSingle(out var acc) ? acc : (BTSAccountImmutable?)null)
|
||||
.Where(acc => acc.HasValue)
|
||||
.Select(acc => acc.Value)
|
||||
.ToDictionary(acc => acc.Id, acc => acc.Name) :
|
||||
new Dictionary<uint, string>(0))
|
||||
.Replay(1)
|
||||
.RefCount();
|
||||
// users
|
||||
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
|
||||
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
|
||||
service.subscribei_admin_data()
|
||||
.Select(u => u.all.TryFirst())
|
||||
.Where(ad => ad.HasValue)
|
||||
.Select(ad => ad.Value.Users
|
||||
.Where(u => user.can_access_user_data(u.Id))
|
||||
.Select(u => new UserTuple(u.Id, u.Name))
|
||||
.ToDictionary(UserTuple.key_fn))
|
||||
.Table(UserTuple.key_fn)
|
||||
.Cache() :
|
||||
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
|
||||
_dispose = on_accounts.Subscribe(map => _account_map = map);
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_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
|
||||
select bts_user;
|
||||
|
||||
IEnumerable<BTSAccountImmutable> active_accounts =
|
||||
from admin_data in update.all
|
||||
from bts_account in admin_data.Accounts
|
||||
where bts_account.IsPresent && _wrapper_subject.Value.can_view_account(bts_account.Name)
|
||||
join account_id in (
|
||||
from bts_user in active_users
|
||||
from account_id in bts_user.UserData.AccountPermissions.AccountPermissions.Keys
|
||||
select account_id
|
||||
) on bts_account.Id equals account_id
|
||||
select bts_account;
|
||||
|
||||
var users = active_users.ToDictionary(u => u.Id, u => u.Name);
|
||||
var accounts = active_accounts.ToDictionary(a => a.Id, a => a.Name);
|
||||
return (users, accounts);
|
||||
}).Subscribe(t => {
|
||||
_account_map = t.accounts;
|
||||
_user_map = t.users;
|
||||
_users_subject.OnNext(_user_map = t.users);
|
||||
_accounts_subject.OnNext(_account_map = t.accounts);
|
||||
});
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
|
||||
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 IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
positions :
|
||||
positions.Filter(on_accounts.Select(map => new Func<T, bool>(p => map.ContainsKey(get_account_id(p)))));
|
||||
public AccountTuple? get_default_account() {
|
||||
foreach (var kv in _account_map) {
|
||||
return new AccountTuple(kv.Key, kv.Value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public uint? get_user_id(string user) => _id_for_name(user, _user_map);
|
||||
public uint? get_account_id(string account) => _id_for_name(account, _account_map);
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, string> get_account_name) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
positions :
|
||||
positions.Filter(on_accounts.Select(map => {
|
||||
var hash = new HashSet<string>(map.Values);
|
||||
return new Func<T, bool>(v => hash.Contains(get_account_name(v)));
|
||||
}));
|
||||
|
||||
public AccountTuple? get_default_account() =>
|
||||
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
static uint? _id_for_name(string name, IReadOnlyDictionary<uint, string> map) {
|
||||
foreach (var kv in map) {
|
||||
if (kv.Value == name)
|
||||
return kv.Key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,15 @@ 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;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -18,8 +23,8 @@ namespace bts.ui {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AboutDaemon(IBus bus, UserTuple user) {
|
||||
_user = user.name;
|
||||
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
|
||||
_user = username;
|
||||
_bus = bus;
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
}
|
||||
@@ -58,4 +63,4 @@ namespace bts.ui {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts.spark {
|
||||
namespace bts.spark
|
||||
{
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
public class ErrorReporter : IShellDaemon, IDisposable {
|
||||
readonly ErrorReportService _svc;
|
||||
@@ -19,9 +21,9 @@ namespace bts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, UserTuple user) {
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
|
||||
_svc = svc;
|
||||
_username = user.name;
|
||||
_username = username;
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
}
|
||||
|
||||
@@ -46,45 +48,6 @@ namespace bts.spark {
|
||||
ErrorMessage = message,
|
||||
});
|
||||
}
|
||||
public async Task report_silently(string contact, string description, string userdata) {
|
||||
var request = append_logs(new ErrorReport {
|
||||
User = _username,
|
||||
ErrorMessage = description,
|
||||
UserDescription = userdata,
|
||||
Contact = contact
|
||||
}.immutable());
|
||||
var reply = await _svc.send_report_async(request);
|
||||
}
|
||||
|
||||
public static ErrorReportImmutable append_logs(ErrorReportImmutable src) {
|
||||
var result = src.CloneToMutable();
|
||||
|
||||
var windows_log = LogUtil.read_windows_event_log();
|
||||
if (!windows_log.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = "Application.evtx.gz",
|
||||
Contents = windows_log
|
||||
});
|
||||
}
|
||||
|
||||
var (data,date) = LogUtil.read_latest_crash_dump();
|
||||
if (!data.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = $"spark-{date.ToString("s")}.dmp.gz",
|
||||
Contents = data
|
||||
});
|
||||
}
|
||||
|
||||
var spark_log = LogUtil.read_logs_compressed();
|
||||
if (!spark_log.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = "log.gz",
|
||||
Contents = spark_log
|
||||
});
|
||||
}
|
||||
return result.immutable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ErrorReporterExtensions {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
@@ -8,10 +7,15 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
using bts.md;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
@@ -21,21 +25,17 @@ namespace bts {
|
||||
readonly Lazy<SyntheticSecurityService> _ss;
|
||||
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
|
||||
|
||||
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, UserTuple user)
|
||||
: base(mbts.config.Configuration.get<md.RelayOptions>().naggle_ms, MDSourceOptions.DeferInit, user) {
|
||||
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;
|
||||
_ss = ss;
|
||||
}
|
||||
|
||||
public int priority => 55;
|
||||
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
IBus bus => _bus.Value;
|
||||
|
||||
public override void initialize() {
|
||||
override public void initialize() {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
@@ -60,8 +60,16 @@ namespace bts {
|
||||
// Post full dictionary of syntheics here
|
||||
bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
_dispose.Add(on_securities.Subscribe(md => bus.post(SparkTags.SecuritiesTag, md)));
|
||||
_dispose.Add( this.on_securities.Subscribe( (md) => {
|
||||
bus.post(SparkTags.SecuritiesTag, md);
|
||||
}));
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
|
||||
public int priority => 55;
|
||||
IBus bus => _bus.Value;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using mbts.ui;
|
||||
using mbts;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.spark {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class OopsDaemon : IShellDaemon, IDisposable {
|
||||
[ImportingConstructor]
|
||||
public OopsDaemon(ErrorReporter e) {
|
||||
var pid = get_pid();
|
||||
if (pid.HasValue) {
|
||||
handle_unclean_shutdown(e, pid.Value);
|
||||
}
|
||||
save_pid();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
clear_pid();
|
||||
}
|
||||
|
||||
void handle_unclean_shutdown(ErrorReporter e, int oldpid) {
|
||||
string extra = "";
|
||||
try {
|
||||
var conf = mbts.config.Configuration.get<bts.config.System>();
|
||||
extra = JsonConvert.SerializeObject(conf, Formatting.Indented);
|
||||
}
|
||||
catch { }
|
||||
Task.Run(() => e.report_silently(
|
||||
$"Host: {Environment.MachineName}",
|
||||
$"pid: {oldpid}\n{extra}",
|
||||
"Automated crash report")
|
||||
);
|
||||
}
|
||||
|
||||
const string _subkey = @"Software\BTS";
|
||||
private void save_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
key.SetValue("pid", System.Diagnostics.Process.GetCurrentProcess().Id, Microsoft.Win32.RegistryValueKind.DWord);
|
||||
}
|
||||
}
|
||||
private void clear_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
key.DeleteValue("pid",false);
|
||||
}
|
||||
}
|
||||
private int? get_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
return key.GetValue("pid") as int?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,11 +43,6 @@ namespace bts.shell {
|
||||
void _show_order_window(Window window) {
|
||||
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
|
||||
window.Resources[kv.Key] = kv.Value;
|
||||
// the dynamic algo 'SecurityKey' template has a ContractPickerPopup that expects the security map to be available as a DynamicResource.
|
||||
var sub = _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => window.Resources[ResourceKeys.Securities] = sec_map);
|
||||
window.Closed += (s, e) => Disposable.dispose(ref sub);
|
||||
window.Show(-200, -160, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@ 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;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.perf {
|
||||
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class ShellLatencyDaemon : IShellDaemon, IDisposable {
|
||||
readonly ShellLatencyService _service;
|
||||
@@ -20,10 +24,10 @@ namespace bts.perf {
|
||||
Ping _pinger = new Ping();
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service, UserTuple user) {
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_username = user.name;
|
||||
_username = user;
|
||||
_hostname = Environment.MachineName;
|
||||
|
||||
_forever_ping_host(_pinger);
|
||||
@@ -101,4 +105,4 @@ namespace bts.perf {
|
||||
Disposable.dispose(ref _subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
HamburgerMenuGeometryKey = nameof(HamburgerMenuGeometryKey),
|
||||
HornGeometryKey = nameof(HornGeometryKey),
|
||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
@@ -173,7 +172,6 @@ namespace mbts.ui {
|
||||
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
|
||||
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
|
||||
public static ImageSource Gear => create_icon(GearGeometryKey);
|
||||
public static ImageSource HamburgerMenu => create_icon(HamburgerMenuGeometryKey);
|
||||
public static ImageSource Horn => create_icon(HornGeometryKey);
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||
|
||||
@@ -272,10 +272,6 @@
|
||||
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HamburgerMenuGeometryKey}">
|
||||
M2.384186E-06,24L32.000004,24 32.000004,26 2.384186E-06,26z M0,11.999998L32,11.999998 32,13.999998 0,13.999998z M2.384186E-06,0L32.000004,0 32.000004,2 2.384186E-06,2z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HornGeometryKey}">
|
||||
M4.0499969,7.0870913C2.9519978,7.0870913 2.0249984,8.4780906 2.0249987,10.125091 2.0249984,11.771091 2.9519978,13.162091 4.0499969,13.162091 5.1479961,13.162091 6.0749954,11.771091 6.0749955,10.125091 6.0749954,8.4780906 5.1479961,7.0870913 4.0499969,7.0870913z M29.975016,3.316155C23.883831,8.2016557,11.829888,8.9691842,8.1184387,9.0896781L8.0151367,9.0928462 8.0175586,9.1059306C8.0716033,9.4352315 8.0999939,9.7760909 8.0999942,10.125091 8.0999939,10.473966 8.0716033,10.814716 8.0175586,11.143921L8.0098858,11.185364 8.193316,11.195448C12.163719,11.420399,23.994581,12.458921,29.975016,17.005057z M31.176758,0.00019051835C31.280359,-0.0022594665 31.391857,0.01889403 31.514975,0.065146674 31.908983,0.21413228 31.999985,0.59114302 31.999985,1.0121596L31.999985,19.236969C31.999985,19.653958 31.913988,20.028956 31.525991,20.179955 31.405998,20.226952 31.450981,20.250023 31.32699,20.250023 31.050991,20.250023 30.778992,20.13601 30.583986,19.925928 25.229074,14.140572 9.6001122,13.269665 7.2865356,13.172559L7.2813506,13.172345 7.1739591,13.343056C6.4304952,14.468533 5.3060585,15.187091 4.0499969,15.187091 1.8169985,15.187091 0,12.916091 0,10.125091 0,7.3330911 1.8169985,5.0620912 4.0499969,5.0620912 5.3060585,5.0620912 6.4304952,5.78065 7.1739591,6.9063042L7.2852713,7.0832756 7.3087114,7.0828188C9.6077337,7.036146 25.141474,6.4939421 30.567997,0.34315929 30.75913,0.12521249 30.948835,0.005580858 31.176758,0.00019051835z
|
||||
</sys:String>
|
||||
|
||||
@@ -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;
|
||||
@@ -12,11 +13,13 @@ using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using static mbts.ui.shell.ProtoBufSerializationHelper;
|
||||
|
||||
namespace bts.shell {
|
||||
namespace bts.shell
|
||||
{
|
||||
[Export(typeof(IShellModule))]
|
||||
class MarketShellModule : IShellModule {
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using mbts.spark;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
readonly struct MarketViewCell {
|
||||
public readonly MarketViewVM.Row row;
|
||||
public readonly MarketViewView.Columns column;
|
||||
|
||||
public MarketViewCell(MarketViewVM.Row row, MarketViewView.Columns column) {
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -19,7 +18,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : DisposableViewModelBase, ISecurityMDRow, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
@@ -27,7 +26,6 @@ namespace mbts.spark {
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
@@ -41,11 +39,9 @@ namespace mbts.spark {
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
bool _is_visible;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) {
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
security_md = smd;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
@@ -58,7 +54,6 @@ namespace mbts.spark {
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
@@ -108,23 +103,12 @@ namespace mbts.spark {
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public SecurityMD security_md { get; }
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public uint security_key => security_md.security.Id;
|
||||
SecurityMD ISecurityMDRow.md => security_md;
|
||||
|
||||
public bool is_visible {
|
||||
get => _is_visible;
|
||||
set => Set(ref _is_visible, value);
|
||||
}
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
@@ -171,30 +155,27 @@ namespace mbts.spark {
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
MDConsumerHandle _md => security_md.md;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (_md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_refresh_inside_working_orers(ref dirty);
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (_md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |=
|
||||
_settlement.set_new(_md.settlement_price) |
|
||||
_session_low.set_new(_md.session_low) |
|
||||
_session_high.set_new(_md.session_high) |
|
||||
_day_change.set_new(last_price - settlement);
|
||||
_settlement.set(md.md.settlement_price);
|
||||
_session_low.set(md.md.session_low);
|
||||
_session_high.set(md.md.session_high);
|
||||
_day_change.set(last_price - settlement);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
@@ -220,7 +201,7 @@ namespace mbts.spark {
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, security_md);
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
@@ -294,7 +275,9 @@ namespace mbts.spark {
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_refresh_inside_working_orers(ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
@@ -304,11 +287,6 @@ namespace mbts.spark {
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
|
||||
void _refresh_inside_working_orers(ref bool dirty) {
|
||||
_inside_bids.on_next_distinct(!double.IsNaN(working_best_bid) && OrderDataHelper.PriceComparer.Compare(working_best_bid, bid_price) >= 0, ref dirty);
|
||||
_inside_asks.on_next_distinct(!double.IsNaN(working_best_ask) && OrderDataHelper.PriceComparer.Compare(working_best_ask, ask_price) <= 0, ref dirty);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
@@ -384,4 +362,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,15 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
@@ -31,94 +17,45 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
/// <summary>
|
||||
/// Viewmodel class designed for <see cref="ui.spark.MarketViewView"/>
|
||||
/// </summary>
|
||||
static readonly IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>().ToReadOnlyList();
|
||||
|
||||
static readonly IComparer<SecurityMD> _sec_md_key_comparer = Comparer<uint>.Default.wrap(SecurityMD.key_fn);
|
||||
static readonly IComparer<Row> _row_key_comparer = _sec_md_key_comparer.wrap<SecurityMD, Row>(r => r.security_md);
|
||||
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ObservableCollection<Row> _rows = new ObservableCollection<Row>();
|
||||
readonly IList<SecurityMD> _wrapped_rows;
|
||||
readonly ListCollectionView _rows_view;
|
||||
readonly Dictionary<uint, Row> _cache = new Dictionary<uint, Row>();
|
||||
readonly Property<Row> _current_row;
|
||||
readonly DeferrableActionWrapper _refresh_filter;
|
||||
Func<Row, bool> _filter;
|
||||
IReadOnlyList<(Columns key, ListSortDirection direction)> _sorting_hierarchy;
|
||||
Color _selection_highlight;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of the <see cref="ui.spark.MarketViewView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bus"></param>
|
||||
/// <param name="orders_model"></param>
|
||||
/// <param name="security_mds_source"></param>
|
||||
/// <param name="use_mouse_state_transitions">Indicates whether or not hyperlink cells should use visual states to animate mouse over (seems to be slower than style triggers, should investigate to be certain).</param>
|
||||
public MarketViewVM(
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
IObservable<TableUpdate<SecurityMD, uint>> security_mds_source,
|
||||
bool use_mouse_state_transitions = false) {
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: 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, 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));
|
||||
comparer = DIYGridSortHelper.build_comparer(sorting_hierarchy, get_column_comparer, Columns.SecurityKey);
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model);
|
||||
r.is_visible = _filter == null || _filter(r);
|
||||
return r;
|
||||
});
|
||||
_current_row = backing<Row>(nameof(current_row));
|
||||
_rows_view = new ListCollectionView(_rows) {
|
||||
Filter = obj => obj is Row r && r.is_visible,
|
||||
IsLiveFiltering = true
|
||||
};
|
||||
_rows_view.LiveFilteringProperties.Add(nameof(Row.is_visible));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true);
|
||||
add_hl_working_orders_col(
|
||||
Columns.WorkingBids,
|
||||
working_col_header,
|
||||
nameof(Row.working_bids_text),
|
||||
nameof(Row.working_best_bid),
|
||||
working_bid_volume_filter,
|
||||
BTSColors.MarketData.bid);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
|
||||
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), bid_merged_size_filter, BTSColors.MarketData.bid);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), ask_merged_size_filter, BTSColors.MarketData.ask);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
|
||||
add_hl_working_orders_col(
|
||||
Columns.WorkingAsks,
|
||||
working_col_header,
|
||||
nameof(Row.working_asks_text),
|
||||
nameof(Row.working_best_ask),
|
||||
working_ask_volume_filter,
|
||||
BTSColors.MarketData.ask);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
@@ -128,8 +65,8 @@ namespace mbts.spark {
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
RenderTextColumn<Row> add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, HorizontalAlignment h_alignment = HorizontalAlignment.Right) {
|
||||
var col = new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
@@ -138,85 +75,31 @@ namespace mbts.spark {
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
};
|
||||
columns.Add(col);
|
||||
return col;
|
||||
}
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, IDataGridColumnFilter filter, Color foreground, Color? background) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
header,
|
||||
price_binding(prop),
|
||||
prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
DefaultColumnWidths.MViewPrice,
|
||||
foreground,
|
||||
background,
|
||||
tooltip: _order_ticket_tooltip(col_key));
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string sort_prop, string text_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
merged_size_col_header,
|
||||
new Binding(text_prop),
|
||||
sort_prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
DefaultColumnWidths.ImpliedDirectMergedSize,
|
||||
foreground,
|
||||
tooltip: _order_ticket_tooltip(col_key));
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
HyperlinkElementColumn<Columns> add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
header,
|
||||
new Binding(text_prop),
|
||||
sort_prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.IntMinMax,
|
||||
working_orders_col_width,
|
||||
foreground,
|
||||
tooltip: $"Show {_orderbook_title(col_key).desc}");
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
HyperlinkElementColumn<Columns> add_hl_col(
|
||||
Columns col_key,
|
||||
string header,
|
||||
BindingBase binding,
|
||||
string prop,
|
||||
IDataGridColumnFilter filter,
|
||||
string filter_template,
|
||||
double width,
|
||||
Color? foreground = null,
|
||||
Color? background = null,
|
||||
HorizontalAlignment h_alignment = HorizontalAlignment.Right,
|
||||
string tooltip = null) {
|
||||
|
||||
var col = new HyperlinkElementColumn<Columns>(
|
||||
col_key, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
TextBinding = binding,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment,
|
||||
HyperlinkClickMode = HyperlinkCellClickMode.Anywhere
|
||||
};
|
||||
col.view_generated += on_view_generated;
|
||||
columns.Add(col);
|
||||
return col;
|
||||
//
|
||||
void on_view_generated(HyperlinkCell cell_view) {
|
||||
cell_view.SetValue(ColumnKeyProperty, col_key);
|
||||
cell_view.ToolTip = tooltip;
|
||||
if (use_mouse_state_transitions)
|
||||
init_hyperlink_hover_animation(cell_view, col);
|
||||
}
|
||||
}
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
@@ -224,134 +107,78 @@ namespace mbts.spark {
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
// row vm filter
|
||||
// Althoguh securities are filtered at the row 'model' level (SecurityMD), the column base filters have to be applied at the row vm level,
|
||||
// because they access market data, which is polled periodically and cached in the row vm.
|
||||
_refresh_filter = new DeferrableActionWrapper(() => _filter_changed?.Invoke());
|
||||
var row_filter_source = Observable.Create<Func<Row, bool>>(o => {
|
||||
notify();
|
||||
_filter_changed += notify;
|
||||
return Disposable.Create(() => _filter_changed -= notify);
|
||||
void notify() =>
|
||||
o.OnNext(get_filter_states().build_predicate());
|
||||
});
|
||||
foreach (var f in _all_filters) {
|
||||
f.filter_changed += _refresh_filter.execute;
|
||||
}
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
row_filter_source.Subscribe(f => {
|
||||
// the rows are polled every 100 ms, just set the filter and let the refresh take care of the rest.
|
||||
_filter = f;
|
||||
}),
|
||||
security_mds_source.Subscribe(_on_securities),
|
||||
ObservableAutoWrapper.connect_item_hooks(_rows, r => r, true),
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
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, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
void init_hyperlink_hover_animation(HyperlinkCell cell_view, ColumnBase col) {
|
||||
// background timeline
|
||||
var hover_bg_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view,
|
||||
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
|
||||
new Binding(nameof(ColumnBase.background_color)) {
|
||||
Source = col,
|
||||
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x1A) }
|
||||
});
|
||||
// background pressed timeline
|
||||
var pressed_bg_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view,
|
||||
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
|
||||
new Binding(nameof(ColumnBase.background_color)) {
|
||||
Source = col,
|
||||
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x25) }
|
||||
});
|
||||
// border timeline
|
||||
var bd_brush = new SolidColorBrush();
|
||||
BindingOperations.SetBinding(bd_brush, SolidColorBrush.ColorProperty, new Binding(nameof(ColumnBase.background_color)) { Source = col });
|
||||
cell_view.BorderBrush = bd_brush;
|
||||
cell_view.BorderThickness = new Thickness(1);
|
||||
var hover_bd_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view, new PropertyPath("(0).(1)", HyperlinkCell.BorderBrushProperty, SolidColorBrush.ColorProperty), Colors.Black.with(0x60));
|
||||
// mouse states
|
||||
VisualStateManager.GetVisualStateGroups(cell_view).Add(
|
||||
VisualStateHelper.create_group("MouseStates",
|
||||
new VisualState { Name = "Normal" },
|
||||
VisualStateHelper.create_state("MouseOver", hover_bg_timeline, hover_bd_timeline),
|
||||
VisualStateHelper.create_state("Pressed", pressed_bg_timeline, hover_bd_timeline)));
|
||||
// state groups manager
|
||||
StateManager.SetGroups(cell_view, new StateGroupsManager {
|
||||
StateDefinitions = new StateDefinitionCollection {
|
||||
new MouseStateDefinition { MouseSource = cell_view }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChange<Row> current_row_changed { add => _current_row.Changed += value; remove => _current_row.Changed -= value; }
|
||||
event Action _filter_changed;
|
||||
|
||||
public ICollectionView rows_view => _rows_view;
|
||||
public ObservableCollection<Row> rows => _rows;
|
||||
public ObservableCollection<Row> selected_rows { get; } = new ObservableCollection<Row>();
|
||||
public Color selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public Row current_row { get => _current_row.Value; set => _current_row.set(value); }
|
||||
|
||||
internal IBus bus => _bus;
|
||||
internal OrdersModel orders_model => _orders_model;
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public IReadOnlyList<(Columns key, ListSortDirection direction)> sorting_hierarchy {
|
||||
get => _sorting_hierarchy ?? (_sorting_hierarchy = Array.Empty<(Columns, ListSortDirection)>());
|
||||
set {
|
||||
_sorting_hierarchy = value;
|
||||
comparer = DIYGridSortHelper.build_comparer(value, get_column_comparer, Columns.SecurityKey);
|
||||
DIYGridSortHelper.reset_sorting(columns, value);
|
||||
_rows_view.CustomSort = comparer.to_non_generic();
|
||||
}
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public IComparer<Row> comparer { get; private set; }
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _all_filters =>
|
||||
_filter_getters.Select(g => g(this));
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSpd: return wrap(r => r.last_price_from_spread);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
@@ -380,21 +207,36 @@ namespace mbts.spark {
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
public (OrderBookVM vm, string context, string title, Color fg) orderbook_params(SecurityImmutable security, Columns column) {
|
||||
var (title_desc, fg) = _orderbook_title(column);
|
||||
var filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = SecurityCriteria.from_keys(new[] { security.Id })
|
||||
};
|
||||
switch (column) {
|
||||
case Columns.WorkingBids: filters.sides = new[] { Side.Buy }; break;
|
||||
case Columns.WorkingAsks: filters.sides = new[] { Side.Sell }; break;
|
||||
}
|
||||
var vm = _mk_orderbook_vm_common(filters, new[] {
|
||||
OrderBookGridView.ColumnKeys.Security,
|
||||
OrderBookGridView.ColumnKeys.Side
|
||||
});
|
||||
return (vm, $"{security}", title_desc, fg);
|
||||
}
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
@@ -408,7 +250,6 @@ namespace mbts.spark {
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
@@ -428,102 +269,14 @@ namespace mbts.spark {
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void load_filters(FilterStates filters) {
|
||||
if (filters != null) {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.WorkingBids: return ("Working Buy Orders", BTSColors.MarketData.bid);
|
||||
case Columns.WorkingAsks: return ("Working Sell Orders", BTSColors.MarketData.ask);
|
||||
default: return ("Working Orders", Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
|
||||
grid.with(_hide_cols(grid.columns, columns));
|
||||
|
||||
static IEnumerable<GridColumnSettings> _hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<OrderBookGridView.ColumnKeys> columns) {
|
||||
// hide columns who's data is implied by the grouping
|
||||
var col_hash = new HashSet<string>(columns.Select(k => k.ToString()));
|
||||
foreach (var c in cols)
|
||||
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
|
||||
}
|
||||
|
||||
static string _order_ticket_tooltip(Columns key) {
|
||||
var bid_or_ask = Regex.Match(key.ToString(), "(Bid|Ask)");
|
||||
return bid_or_ask.Success ? $"Order Ticket @ {bid_or_ask.Value}" : "Order Ticket";
|
||||
}
|
||||
|
||||
OrderBookVM _mk_orderbook_vm_common(bts.shell.orderbook.FilterStates filters, IEnumerable<OrderBookGridView.ColumnKeys> hide_cols, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(filters, bts.shell.orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
|
||||
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_cols);
|
||||
var settings_man = new WindowSettingsManager<bts.shell.orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = orders_source == null ?
|
||||
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
|
||||
new OrderBookVM(_bus, null, _orders_model, orders_source, settings_man, true);
|
||||
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
|
||||
obvm.columns.Insert(0, c);
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
}
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _rows) {
|
||||
r.refresh();
|
||||
r.is_visible = _filter == null || _filter(r);
|
||||
}
|
||||
}
|
||||
|
||||
Row _row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
|
||||
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
|
||||
selected_rows.RemoveSorted(e.RemovedItems.OfType<Row>(), _row_key_comparer);
|
||||
selected_rows.InsertSorted(e.AddedItems.OfType<Row>(), _row_key_comparer);
|
||||
}
|
||||
|
||||
void _on_securities(TableUpdate<SecurityMD, uint> table) {
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
foreach (var smd in table.removed) {
|
||||
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
|
||||
Log.assert(i >= 0);
|
||||
_wrapped_rows.RemoveAt(i);
|
||||
}
|
||||
foreach (var smd in table.added) {
|
||||
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
|
||||
Log.assert(i < 0);
|
||||
_wrapped_rows.Insert(~i, smd);
|
||||
}
|
||||
sw.Stop();
|
||||
Log.Ui.info($"MarketView update took {sw.ElapsedMilliseconds:0.00}ms.");
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,8 @@
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.UnsafeWindow="{Binding Path='(u:WindowStyle.UnsafeWindow)', RelativeSource={RelativeSource AncestorType=bv:SparkWindowView}}"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -18,54 +13,8 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="c:HyperlinkCell">
|
||||
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_brush', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Command" Value="{x:Static usp:MarketViewView.CellClickCommand}"/>
|
||||
<Setter Property="CommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:MarketViewView.CellConverter}">
|
||||
<Binding Path=""/>
|
||||
<Binding Path="(c:DIYGrid.ColumnKey)" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=usp:MarketViewView}}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOverClickArea" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush>
|
||||
<SolidColorBrush.Color>
|
||||
<MultiBinding Converter="{x:Static cv:ColorBlendMultiConverter.Instance}">
|
||||
<Binding Path="SelectionHighlight" RelativeSource="{RelativeSource AncestorType=c:DIYGrid}"/>
|
||||
<Binding Path="(c:DIYGrid.Column).background_color" RelativeSource="{RelativeSource AncestorType=c:HyperlinkCell}"/>
|
||||
</MultiBinding>
|
||||
</SolidColorBrush.Color>
|
||||
</SolidColorBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<Binding Path="SelectionHighlight" RelativeSource="{RelativeSource AncestorType=c:DIYGrid}">
|
||||
<Binding.Converter>
|
||||
<cv:ValueConverterPipe>
|
||||
<cv:ColorWithConverter A="255"/>
|
||||
<cv:ColorToBrushConverter/>
|
||||
</cv:ValueConverterPipe>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:MarketViewView.CellClickCommand}" Executed="{x:Static usp:MarketViewView.CellClickExecuted}"/>
|
||||
</UserControl.CommandBindings>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -73,14 +22,13 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
SelectionHighlight="{Binding selection_highlight, Mode=OneWay}"
|
||||
SyncColumnSortDirectionWithSortDescriptions="False"
|
||||
>
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
|
||||
@@ -1,40 +1,17 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
|
||||
|
||||
MarketViewVM _vm;
|
||||
DIYGridPopupManager _popup_man_backing;
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
this.ConnectDataContext<MarketViewVM>(vm => _vm = vm);
|
||||
AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
|
||||
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
}
|
||||
|
||||
public enum Columns {
|
||||
None,
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
LastSpd,
|
||||
LastSize,
|
||||
Position,
|
||||
RawDayPosition,
|
||||
@@ -61,100 +38,14 @@ namespace mbts.ui.spark {
|
||||
MicCode
|
||||
}
|
||||
|
||||
public static ExecutedRoutedEventHandler
|
||||
CellClickExecuted => CommandImpl.cell_click;
|
||||
|
||||
public static IMultiValueConverter CellConverter =>
|
||||
ValueConverter.create<MarketViewVM.Row, Columns, MarketViewCell>((r, c) => new MarketViewCell(r, c));
|
||||
|
||||
IBus _bus => _vm.bus;
|
||||
OrdersModel _model => _vm.orders_model;
|
||||
DIYGridPopupManager _popup_man =>
|
||||
_popup_man_backing ?? (_popup_man_backing = DIYGridPopupManager.create(this));
|
||||
|
||||
public void show_mini_orderbook(SecurityMD security_md) =>
|
||||
_show_mini_orderbook(security_md, Columns.None);
|
||||
|
||||
protected override void OnLostMouseCapture(MouseEventArgs e) {
|
||||
if (e.LeftButton == MouseButtonState.Pressed && Mouse.Captured == null) {
|
||||
if (this.ChildAtPoint(e.GetPosition(this), out DIYGridRowsPresenter presenter))
|
||||
presenter.begin_explicit_drag();
|
||||
}
|
||||
}
|
||||
|
||||
void _on_cell_click(MarketViewVM.Row row, Columns col) {
|
||||
switch (col) {
|
||||
case Columns.WorkingAsks:
|
||||
case Columns.WorkingBids:
|
||||
_show_mini_orderbook(row.security_md, col);
|
||||
break;
|
||||
case Columns.Bid:
|
||||
_show_order_ticket(row.security, row.bid_price);
|
||||
break;
|
||||
case Columns.BidMergedSize: _show_order_ticket(row.security, row.bid_price, row.bid_merged_size); break;
|
||||
case Columns.BidDirectSize: _show_order_ticket(row.security, row.bid_price, row.bid_direct_size); break;
|
||||
case Columns.BidImpliedSize: _show_order_ticket(row.security, row.bid_price, row.bid_implied_size); break;
|
||||
case Columns.Ask:
|
||||
_show_order_ticket(row.security, row.ask_price);
|
||||
break;
|
||||
case Columns.AskMergedSize: _show_order_ticket(row.security, row.ask_price, row.ask_merged_size); break;
|
||||
case Columns.AskDirectSize: _show_order_ticket(row.security, row.ask_price, row.ask_direct_size); break;
|
||||
case Columns.AskImpliedSize: _show_order_ticket(row.security, row.ask_price, row.ask_implied_size); break;
|
||||
case Columns.Last:
|
||||
_show_order_ticket(row.security, row.last_price);
|
||||
break;
|
||||
case Columns.LastSize:
|
||||
_show_order_ticket(row.security, row.last_price, row.last_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_mini_orderbook(SecurityMD security_md, Columns col) {
|
||||
var (vm, context, title, fg) = _vm.orderbook_params(security_md.security, col);
|
||||
_show_popup(vm, new AuxiliaryDataHeader {
|
||||
context = context,
|
||||
title = title,
|
||||
title_foreground = fg
|
||||
});
|
||||
}
|
||||
|
||||
async void _show_order_ticket(SecurityImmutable security, double price = double.NaN, uint size = 0) {
|
||||
var sec_md = await _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Select(sec_map => sec_map.TryGetValue(security.Id, out var smd) ? smd : (SecurityMD?)null)
|
||||
.SkipWhile(smd => !smd.HasValue)
|
||||
.FirstOrDefaultAsync();
|
||||
if (sec_md.HasValue) {
|
||||
var resources = new OrderTicketResources();
|
||||
resources.MergedDictionaries.Add(new AuxiliaryDataResources());
|
||||
var ticket_vm = new NewOrderTicketVM(
|
||||
_model, sec_md.Value, _model.factory.order(security), price, size, Observable.Return(mbts.spark.LadderSettings.Default.calculator_buttons));
|
||||
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
|
||||
}
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header) =>
|
||||
_show_popup(content, header, new AuxiliaryDataResources());
|
||||
|
||||
void _show_popup(object content, object header, ResourceDictionary resources) {
|
||||
var view = new HeaderedContentControl {
|
||||
Content = content,
|
||||
Header = header,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Resources = resources ?? new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
_popup_man.show(view);
|
||||
}
|
||||
|
||||
static class CommandImpl {
|
||||
public static void cell_click(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (sender is MarketViewView mview && e.Parameter is MarketViewCell cell)
|
||||
mview._on_cell_click(cell.row, cell.column);
|
||||
e.Handled = true;
|
||||
}
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
|
||||
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
}
|
||||
}
|
||||
|
||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
|
||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
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;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
@@ -22,14 +21,6 @@ using System.Windows;
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ MarketViewView.Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ MarketViewView.Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ MarketViewView.Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ MarketViewView.Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -41,7 +32,6 @@ namespace mbts.spark {
|
||||
SecurityCriteria
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
SimpleGridSettingsManager<MarketViewVM.Row> _grid_settings_manager;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
@@ -52,16 +42,13 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override MarketViewWindowFactory.StartupData SaveState =>
|
||||
_state = new MarketViewWindowFactory.StartupData(
|
||||
_state.security_criteria,
|
||||
_vm.get_filter_states(),
|
||||
_settings_manager.instance_settings,
|
||||
_vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == System.ComponentModel.ListSortDirection.Ascending))
|
||||
.ToList());
|
||||
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
|
||||
|
||||
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
@@ -81,33 +68,43 @@ namespace mbts.spark {
|
||||
return _selected_security_criteria_backing;
|
||||
}
|
||||
}
|
||||
|
||||
SecurityCriteria _sorted_selected_security_criteria => _sorted_selected_security_criteria_backing ?? (
|
||||
_sorted_selected_security_criteria_backing = _vm != null ?
|
||||
new SecurityCriteria(_vm.selected_rows.Ordered(_vm.comparer).Select(r => r.security)) :
|
||||
null);
|
||||
SecurityCriteria _sorted_selected_security_criteria {
|
||||
get {
|
||||
if (_sorted_selected_security_criteria_backing == null) {
|
||||
if (_vm != null) {
|
||||
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
|
||||
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
|
||||
foreach (var sc in sorting_criteria) {
|
||||
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
|
||||
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
|
||||
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
|
||||
if (col_comp != null) {
|
||||
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
|
||||
}
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
var comp = comp_builder.to_comparer();
|
||||
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
}
|
||||
}
|
||||
return _sorted_selected_security_criteria_backing;
|
||||
}
|
||||
}
|
||||
|
||||
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
_vm = new MarketViewVM(
|
||||
_bus,
|
||||
_orders_model,
|
||||
_bus.subscribe(update_securities_cmd.tag)
|
||||
.StartWith(_state.security_criteria)
|
||||
.SelectManyInSeries(c => c.subscribe_md_map(_bus))
|
||||
.BufferLatest(TimeSpan.FromSeconds(1))
|
||||
.Table(SecurityMD.key_fn)
|
||||
.Where(t => t.has_changes())
|
||||
.OnDispatcher());
|
||||
_vm.load_filters(_state.filters);
|
||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
_view._grid.Sorting += on_grid_sorting;
|
||||
_grid_settings_manager = new SimpleGridSettingsManager<MarketViewVM.Row>(_vm.columns, _col_settings_flags, _vm.rows);
|
||||
_grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
|
||||
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
|
||||
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
|
||||
@@ -138,22 +135,11 @@ namespace mbts.spark {
|
||||
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
|
||||
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 show_mini_ob_cmd = ObservantCommand.create<SecurityMD?, SecurityMD>(_view.show_mini_orderbook, s => s.HasValue, s => s.Value, out var show_mini_ob_observer);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
// commands
|
||||
var current_sec_md_src = Observable.Create<SecurityMD?>(o => {
|
||||
notify(null, null);
|
||||
_vm.current_row_changed += notify;
|
||||
return Disposable.Create(() => _vm.current_row_changed -= notify);
|
||||
void notify(MarketViewVM.Row oldval, MarketViewVM.Row newval) {
|
||||
if (newval != null)
|
||||
o.OnNext(newval.security_md);
|
||||
else o.OnNext(null);
|
||||
}
|
||||
});
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_rows.Observe().Select(_ => _vm.selected_rows.WrapSelectorRO(r => r.security_md)), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_sec_md_src, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
new SparkCommandCommand("Working Orders", Icons.Orders, show_mini_ob_cmd),
|
||||
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),
|
||||
show_fills_cmd_wrapper,
|
||||
show_tas_cmd_wrapper,
|
||||
show_synth_create_cmd_wrapper,
|
||||
@@ -164,7 +150,7 @@ namespace mbts.spark {
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _grid_settings_manager.create_vm(
|
||||
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()))
|
||||
@@ -177,19 +163,16 @@ namespace mbts.spark {
|
||||
}))
|
||||
};
|
||||
//
|
||||
_vm.selected_rows.CollectionChanged += on_selection_changed;
|
||||
_subscriptions = new CompositeDisposable(
|
||||
current_sec_md_src.Subscribe(show_mini_ob_observer),
|
||||
_settings_manager.subscribe_current().OnDispatcher().Subscribe(new CompositeObserver<GridSettings>(_grid_settings_manager) {
|
||||
s => _vm.selection_highlight = s.selection_highlight
|
||||
})) {
|
||||
_vm.selection_changed += on_selection_changed;
|
||||
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
|
||||
() => {
|
||||
_vm.selected_rows.CollectionChanged -= on_selection_changed;
|
||||
_vm.selection_changed -= on_selection_changed;
|
||||
_view._grid.Sorting -= on_grid_sorting;
|
||||
}
|
||||
};
|
||||
|
||||
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
|
||||
// invalidate selected security criteria
|
||||
_selected_security_criteria_backing = null;
|
||||
_sorted_selected_security_criteria_backing = null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.proto;
|
||||
using bts.settings;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -101,7 +100,6 @@ namespace bts.shell {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings volume => new GridColumnSettings(col_id(Columns.Volume), DefaultColumnHeaders.Volume, width: DefaultColumnWidths.Volume);
|
||||
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
|
||||
public static GridColumnSettings last_from_spread => new GridColumnSettings(col_id(Columns.LastSpd), DefaultColumnHeaders.S, width: DefaultColumnWidths.Price, is_visible: false);
|
||||
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
|
||||
@@ -140,7 +138,6 @@ namespace bts.shell {
|
||||
yield return security;
|
||||
yield return volume;
|
||||
yield return last;
|
||||
yield return last_from_spread;
|
||||
yield return last_size;
|
||||
yield return position;
|
||||
yield return raw_day_position;
|
||||
@@ -227,49 +224,7 @@ namespace bts.shell {
|
||||
[ProtoMember(25)]
|
||||
public string strip_name;
|
||||
[ProtoMember(26)]
|
||||
public string last_price_from_spread;
|
||||
[ProtoMember(28)]
|
||||
public string mic_code;
|
||||
|
||||
public Func<MarketViewVM.Row, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
// security
|
||||
pb.add_regex(security, r => r.security.ToString());
|
||||
pb.add_regex(security_key, r => r.security_key.ToString());
|
||||
// bid
|
||||
pb.add_numeric(bid_price, r => r.bid_price);
|
||||
pb.add_numeric(bid_merged_size, r => r.bid_merged_size);
|
||||
pb.add_numeric(bid_direct_size, r => r.bid_direct_size);
|
||||
pb.add_numeric(bid_implied_size, r => r.bid_implied_size);
|
||||
pb.add_numeric(working_bid_volume, r => r.working_bid_volume);
|
||||
// ask
|
||||
pb.add_numeric(ask_price, r => r.ask_price);
|
||||
pb.add_numeric(ask_implied_size, r => r.ask_implied_size);
|
||||
pb.add_numeric(ask_direct_size, r => r.ask_direct_size);
|
||||
pb.add_numeric(ask_merged_size, r => r.ask_merged_size);
|
||||
pb.add_numeric(working_ask_volume, r => r.working_ask_volume);
|
||||
// last
|
||||
pb.add_numeric(last_price, r => r.last_price);
|
||||
pb.add_regex(last_price_from_spread, r => r.last_price_from_spread.ToString());
|
||||
pb.add_numeric(last_size, r => r.last_size);
|
||||
// position
|
||||
pb.add_numeric(position, r => r.position);
|
||||
pb.add_numeric(raw_day_position, r => r.raw_day_position);
|
||||
// market
|
||||
pb.add_numeric(day_change, r => r.day_change);
|
||||
pb.add_numeric(settlement, r => r.settlement);
|
||||
pb.add_numeric(session_low, r => r.session_low);
|
||||
pb.add_numeric(session_high, r => r.session_high);
|
||||
pb.add_numeric(volume, r => r.volume);
|
||||
// ice
|
||||
pb.add_regex(market_description, r => r.market_description);
|
||||
pb.add_regex(cleared_alias, r => r.cleared_alias);
|
||||
pb.add_regex(product_description, r => r.product_description);
|
||||
pb.add_regex(hub_alias, r => r.hub_alias);
|
||||
pb.add_regex(strip_name, r => r.strip_name);
|
||||
pb.add_regex(mic_code, r => r.mic_code);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
|
||||
// migration rules
|
||||
@@ -312,11 +267,5 @@ namespace bts.shell {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
|
||||
class EnsureSelectionHighlightTranslucency : IMarketViewGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row> { }
|
||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
@@ -11,7 +10,6 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -40,7 +38,7 @@ namespace bts.shell {
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
|
||||
_settings_hub.create_time_and_sales_settings_manager(state.grid)));
|
||||
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
|
||||
|
||||
public async void show(SecurityCriteria criteria) {
|
||||
var w = await load(new StartupData(criteria));
|
||||
@@ -124,11 +122,5 @@ namespace bts.shell {
|
||||
[ProtoMember(7)]
|
||||
public co2.AggressorSide[] aggressor_sides;
|
||||
}
|
||||
|
||||
[Export(typeof(ITimeAndSalesGridSettingsMigrationRule))]
|
||||
class EnsureSelectionHighlightTranslucency : ITimeAndSalesGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
|
||||
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
|
||||
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
|
||||
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
|
||||
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
|
||||
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
|
||||
return col;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -20,36 +18,11 @@ namespace mbts.spark {
|
||||
readonly uint _user_id;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, AdminModel admin_model, ErrorReporter error, UserTuple user) {
|
||||
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;
|
||||
// synthetic tuples
|
||||
var strategy_source = _bus.subscribe(SparkTags.SyntheticstrategiesTag)
|
||||
.Select(map => MappedDictionary.create<uint, ExecutionStrategyImmutable, (uint security_id, ExecutionStrategyImmutable strategy)>(map, (k, v) => (k, v)))
|
||||
.Table(tup => tup.security_id);
|
||||
|
||||
var synthetic_source = _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Select(map => new FilteredDictionary<uint, SecurityMD>(map, smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic))
|
||||
.Table(smd => smd.security.Id);
|
||||
|
||||
var synthetic_user_join = synthetic_source.LeftJoin(
|
||||
admin_model.all_user_tuples,
|
||||
SecurityMD.key_fn,
|
||||
UserTuple.key_fn,
|
||||
smd => smd.security.Synthetic.UserId,
|
||||
(smd, sid, ut, uid) => new SyntheticTuple(smd.security, ut.name, null),
|
||||
(smd, sid, uid) => new SyntheticTuple(smd.security, null, null),
|
||||
SyntheticTuple.key_fn);
|
||||
|
||||
synthetic_tuples = synthetic_user_join.LeftJoin(
|
||||
strategy_source,
|
||||
SyntheticTuple.key_fn,
|
||||
tup => tup.security_id,
|
||||
(synth_tup, id, strat_tup) => new SyntheticTuple(synth_tup.security, synth_tup.user_name, strat_tup.strategy),
|
||||
(synth_tup, id) => synth_tup,
|
||||
SyntheticTuple.key_fn);
|
||||
_user_id = user_id;
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
|
||||
@@ -63,8 +36,6 @@ namespace mbts.spark {
|
||||
.LeftJoin(strategies, (security, strategy) => (new ExecutionStrategyImmutable?(strategy), security), security => (new ExecutionStrategyImmutable?(), security))
|
||||
);
|
||||
|
||||
public IObservable<TableUpdate<SyntheticTuple, uint>> synthetic_tuples { get; }
|
||||
|
||||
public IObservable<ExecutionStrategyImmutable> security_strategy(uint security_key) =>
|
||||
strategy.TrySelect(s => {
|
||||
bool success = s.TryGetValue(security_key, out ExecutionStrategyImmutable result);
|
||||
|
||||
@@ -7,18 +7,11 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for SyntheticManagementView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticManagementView : UserControl {
|
||||
public enum Columns {
|
||||
Name,
|
||||
Description,
|
||||
n_legs,
|
||||
n_working_orders,
|
||||
SecurityKey,
|
||||
User
|
||||
}
|
||||
|
||||
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
|
||||
public SyntheticManagementView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
@@ -28,7 +27,7 @@ namespace mbts.spark {
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
@@ -67,8 +66,8 @@ namespace mbts.spark {
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
current_row_connectable_builder.create(observe_row_strategy),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_observable.Select(s => s.HasValue ? ImmutableHashSet<uint>.Empty.Add(s.Value.Id) : null)), id => id != null
|
||||
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -8,7 +7,6 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
@@ -35,8 +33,13 @@ namespace bts.shell {
|
||||
public async void show(SparkTags.Nothing nothing) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
|
||||
|
||||
IShellWindow _create(StartupData state) =>
|
||||
new SyntheticManagementWindow(this, _bus, _orders_model, state, _settings_hub.create_synth_settings_manager(state.grid));
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.synthetic_management_grid,
|
||||
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
@@ -64,7 +67,6 @@ namespace bts.shell {
|
||||
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
|
||||
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, is_visible: false, width: DefaultColumnWidths.User);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
@@ -79,7 +81,6 @@ namespace bts.shell {
|
||||
yield return n_legs;
|
||||
yield return n_working_orders;
|
||||
yield return security_key;
|
||||
yield return user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,15 +101,6 @@ namespace bts.shell {
|
||||
public UIntMinMaxState n_working_orders;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public uint[] user;
|
||||
}
|
||||
|
||||
// migration rules
|
||||
[Export(typeof(ISyntheticManagementMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : ISyntheticManagementMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
@@ -20,45 +17,43 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.SyntheticManagementView;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementDialogVM : SecurityGrid<SyntheticManagementDialogVM.Row, SyntheticTuple> {
|
||||
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
|
||||
: base(orders_model.spreader.synthetic_tuples.Select(t => t.state.Values), sorting) {
|
||||
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_manager = settings_manager;
|
||||
// init filters
|
||||
user_filter = DiscreteValuesFilterVM.CreateTableUpdate<Row, UserTuple, uint>(r => r.user_tuple, UserTuple.key_fn, out var user_filter_items_observer, out var user_filter_items_view, nameof(Row.user_tuple));
|
||||
user_filter_items_view.CustomSort = Comparer<string>.Default.wrap<string, UserTuple>(ut => ut.name).to_non_generic();
|
||||
if (filters != null) {
|
||||
create_time_filter.load_state(filters.create_time);
|
||||
name_filter.load_state(filters.name);
|
||||
description_filter.load_state(filters.description);
|
||||
n_legs_filter.load_state(filters.n_legs);
|
||||
n_working_orders_filter.load_state(filters.n_working_orders);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
user_filter.load_state(filters.user);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
_settings_manager = settings_manager;
|
||||
// columns
|
||||
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
|
||||
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
|
||||
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
|
||||
columns.Add(new HyperlinkElementColumn<Columns>(
|
||||
Columns.n_working_orders,
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
new Binding(nameof(Row.show_order_book)),
|
||||
n_working_orders_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
id = col_id(Columns.n_working_orders),
|
||||
CommandBinding = new Binding(nameof(Row.show_order_book)),
|
||||
TextBinding = new Binding(nameof(Row.n_working_orders)),
|
||||
Header = DefaultColumnHeaders.N_Working_Orders,
|
||||
Width = DefaultColumnWidths.N_Working_Orders
|
||||
});
|
||||
}
|
||||
);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user_name, nameof(Row.user_name), user_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
@@ -68,39 +63,42 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
});
|
||||
//
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>(), row_vms);
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
|
||||
_resources.AddRange(
|
||||
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
|
||||
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
|
||||
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
|
||||
new Row(_bus, row_model.md, row_model.strategy, _orders_model);
|
||||
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
public IDataGridColumnPersistentFilter<Row, uint[]> user_filter { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() =>
|
||||
_settings_manager.instance_settings == null ? null : grid_settings_manager.create();
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
protected override Row row_factory(SyntheticTuple row_model) =>
|
||||
new Row(_bus, row_model, _orders_model);
|
||||
|
||||
protected override void on_row_removed(Row row) =>
|
||||
row.Dispose();
|
||||
|
||||
FilterStates _get_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -109,19 +107,19 @@ namespace mbts.spark {
|
||||
name = name_filter.get_state(),
|
||||
n_legs = n_legs_filter.get_state(),
|
||||
n_working_orders = n_working_orders_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
user = user_filter.get_state()
|
||||
security_key = security_key_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly SyntheticTuple synthetic_tuple;
|
||||
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly Property<uint> _n_working_orders;
|
||||
|
||||
public Row(IBus bus, SyntheticTuple synthetic_tuple, OrdersModel orders_model) : base(synthetic_tuple.security) {
|
||||
public Row(IBus bus, SecurityMD security_md, ExecutionStrategyImmutable? strategy, OrdersModel orders_model) : base(security_md) {
|
||||
_bus = bus;
|
||||
this.synthetic_tuple = synthetic_tuple;
|
||||
this.strategy = strategy;
|
||||
_n_working_orders = backing(nameof(n_working_orders),
|
||||
orders_model.on_security_orders(security_key)
|
||||
.Select(t => (uint)(t.state
|
||||
@@ -135,23 +133,25 @@ namespace mbts.spark {
|
||||
show_order_book = new DelegateCommand(_show_order_book);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public DateTime create_time => DateTime.MinValue; //TODO
|
||||
public string name { get; }
|
||||
public string user_name => synthetic_tuple.user_name;
|
||||
public UserTuple user_tuple => new UserTuple(synthetic_tuple.security.Synthetic.UserId, user_name);
|
||||
public int n_legs { get; }
|
||||
public string description { get; }
|
||||
public ExecutionStrategyImmutable? strategy => synthetic_tuple.strategy;
|
||||
public bool strategy_present => strategy.HasValue;
|
||||
public uint n_working_orders => _n_working_orders.Value;
|
||||
public ICommand show_ladder { get; }
|
||||
public ICommand show_order_book { get; }
|
||||
public ICommand default_command => show_ladder;
|
||||
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
|
||||
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
@@ -163,8 +163,10 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
|
||||
|
||||
void _show_ladder() =>
|
||||
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet<uint>.Empty.Add(security.Id));
|
||||
_bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
@@ -175,4 +177,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,7 @@ namespace mbts.spark {
|
||||
_vm = new AlgoStudioVM(_model);
|
||||
_vm.load_from(_state);
|
||||
_view = new AlgoStudioView { DataContext = _vm };
|
||||
_resources = new CompositeDisposable(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
_resources = new CompositeDisposable();
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mbts.spark {
|
||||
_error_reporter = error_reporter;
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace mbts.spark {
|
||||
string _template_key;
|
||||
ICommand _set_value_cmd;
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent)
|
||||
: this(algo, param, parent, (param as ITemplateSelectorKey)?.key) { }
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
|
||||
this.param = param;
|
||||
this.algo = algo;
|
||||
|
||||
-40
@@ -247,31 +247,6 @@
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<!--Note: Ideally, this 'securities BindingProxy' would be a static resource, but it only seems to detect the dynamic securities when it is in the visual tree.-->
|
||||
<!--<Grid.Resources>
|
||||
<u:BindingProxy x:Key="SecuritiesBindingProxy" x:Shared="False" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
|
||||
</Grid.Resources>-->
|
||||
<u:BindingProxy x:Name="SecuritiesBindingProxy" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe>
|
||||
<cv:DictionaryLookupConverter/>
|
||||
<x:Static MemberType="cv:ValueConverters" Member="SecuritySelectorText"/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="Value" ElementName="SecuritiesBindingProxy"/>
|
||||
<Binding Path="param.value"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
@@ -345,21 +320,6 @@
|
||||
</usp:PriceEntryControl>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
|
||||
SelectedSecurityKey="{Binding param.value}"
|
||||
ShowDialogSkirt="True"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityKeyHandler}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
|
||||
@@ -118,14 +118,13 @@ namespace mbts.spark {
|
||||
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
|
||||
// all others (easily identified by the enum 'Columns.Foo')
|
||||
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), new TimeFilterVM<FillTuple>(r => r.fill.Timestamp.ToDateTimeOffset().LocalDateTime), DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values.Ordered()), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
|
||||
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
@@ -147,7 +146,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
|
||||
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
last_unfiltered_fills = t;
|
||||
@@ -229,7 +227,6 @@ namespace mbts.spark {
|
||||
case Columns.Exchange: return wrap_default(ft => ft.security.exchange_or_default());
|
||||
case Columns.FillKey: return wrap(ft => ft.fill.FillKey);
|
||||
case Columns.FillKind: return wrap_default(ft => ft.fill.FillKind);
|
||||
case Columns.FillStatus: return wrap_default(ft => ft.fill.FillStatus);
|
||||
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
|
||||
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
|
||||
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
|
||||
@@ -296,7 +293,6 @@ namespace mbts.spark {
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace mbts.ui.spark {
|
||||
Price,
|
||||
Size,
|
||||
FillKind,
|
||||
FillStatus,
|
||||
FillKey,
|
||||
OrderKey,
|
||||
SecurityKey
|
||||
@@ -31,5 +30,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
|
||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
|
||||
}
|
||||
@@ -37,8 +37,7 @@ namespace bts.shell {
|
||||
{ nameof(FillsVM.Row.side), Columns.Side },
|
||||
{ nameof(FillsVM.Row.size), Columns.Size },
|
||||
{ nameof(FillsVM.Row.time_in_force), Columns.TimeInForce },
|
||||
{ nameof(FillsVM.Row.user), Columns.User },
|
||||
{ nameof(FillsVM.Row.fill_status ), Columns.FillStatus },
|
||||
{ nameof(FillsVM.Row.user), Columns.User }
|
||||
};
|
||||
|
||||
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
@@ -72,11 +71,5 @@ namespace bts.shell {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(FillsSettings.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
|
||||
[Export(typeof(IFillsGridSettingsMigrationRule))]
|
||||
class EnsureSelectionHighlightTranslucency : IFillsGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,10 +74,6 @@ namespace bts.shell.fills {
|
||||
[ProtoMember(15)]
|
||||
[DIYGridColumnID(nameof(Columns.Symbol))]
|
||||
public string[] symbol { get; set; }
|
||||
|
||||
[ProtoMember(17)]
|
||||
[DIYGridColumnID(nameof(Columns.FillStatus))]
|
||||
public Fill.Types.FillStatus[] fill_status { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -100,7 +96,6 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
|
||||
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
@@ -51,7 +50,6 @@ namespace bts.shell {
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderAutoLookupTag).Subscribe(_show_windows),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
@@ -275,21 +273,7 @@ namespace bts.shell {
|
||||
});
|
||||
}
|
||||
|
||||
async void _show_windows(IImmutableSet<uint> securities) {
|
||||
if (securities == null)
|
||||
return;
|
||||
var start_time = DateTime.Now;
|
||||
do {
|
||||
var found = securities.KeyJoin(_securities).ToList();
|
||||
securities = securities.RemoveRange(found.Select(SecurityMD.key_fn));
|
||||
show_windows(found);
|
||||
if (securities.Count == 0)
|
||||
break;
|
||||
await Task.Delay(500);
|
||||
} while ((DateTime.Now - start_time) < _ladder_conf.security_timeout);
|
||||
}
|
||||
|
||||
void show_windows(IEnumerable<SecurityMD> securities) {
|
||||
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
|
||||
int n_allowed = (int)_ladder_conf.max_open_ladders - _wm.list_windows().Where(w => w.window_type == _window_type).Count();
|
||||
int n_opened = 0;
|
||||
foreach (var s in securities) {
|
||||
|
||||
@@ -12,6 +12,7 @@ using mbts.ui.spark.algo;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -255,8 +256,9 @@ namespace mbts.spark {
|
||||
DependencyProperty.Register("Orders", typeof(ObservableCollection<LadderOrderVM>), typeof(LadderGridHost), new PropertyMetadata(null, orders_changed));
|
||||
|
||||
static void orders_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is LadderGridHost lgh)
|
||||
WPFHelper.manage_collection_change(e, lgh.orders_collection_changed);
|
||||
LadderGridHost lgh = d as LadderGridHost;
|
||||
WPFHelper.manage_collection_change(e, lgh.orders_collection_changed, src => lgh._ordershost.clear_orders(),
|
||||
src => lgh.orders_collection_changed(lgh, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)src)));
|
||||
}
|
||||
|
||||
void orders_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
@@ -84,7 +80,6 @@ namespace mbts.spark {
|
||||
_ladder.visible_columns_changed += () => _column_headers_presenter.Columns.SetItems(
|
||||
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
|
||||
this.vm = vm;
|
||||
this.ConnectDataContext<LadderVM>(lvm => lvm.bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => Resources[ResourceKeys.Securities] = sec_map));
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
|
||||
@@ -62,15 +62,15 @@
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
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 u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<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}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -6,6 +6,12 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for ManualFillRowEditor.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillRowEditor : UserControl {
|
||||
public const string
|
||||
SecuritiesKey = nameof(SecuritiesKey),
|
||||
AccountsKey = nameof(AccountsKey),
|
||||
UsersKey = nameof(UsersKey),
|
||||
SidesKey = nameof(SidesKey);
|
||||
|
||||
public static readonly RoutedCommand
|
||||
CommitCommand = new RoutedCommand(),
|
||||
CancelCommand = new RoutedCommand();
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace bts.shell.manual_fills {
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
|
||||
security_map = m;
|
||||
RaisePropertyChanged(nameof(security_map));
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
|
||||
|
||||
@@ -50,11 +50,11 @@ namespace mbts.ui.spark {
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
|
||||
// dynamic resources
|
||||
row_editor.Resources[ResourceKeys.Accounts] = vm.accounts;
|
||||
row_editor.Resources[ResourceKeys.Users] = vm.users;
|
||||
row_editor.Resources[ResourceKeys.Sides] = vm.sides;
|
||||
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
|
||||
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
|
||||
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
|
||||
FocusManager.SetIsFocusScope(row_editor, true);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ResourceKeys.Securities] = v);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
|
||||
// popup adorner
|
||||
var popup_adorner = new PopupAdorner(
|
||||
data_grid, data_grid,
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
DataContext = vm;
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public MiniOrderBookRowView() : base() {
|
||||
FocusManager.SetIsFocusScope(this, true);
|
||||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace mbts.spark {
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values.Ordered()),
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
ort => ort.order.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
@@ -133,7 +133,7 @@ namespace mbts.spark {
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values.Ordered()),
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
ort => ort.order.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
@@ -650,13 +650,12 @@ namespace mbts.spark {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
|
||||
: base(Columns.LastMessage, filter, DataGridFilterKnownTemplates.Regex) {
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = DefaultColumnWidths.LastMessage;
|
||||
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace mbts.spark {
|
||||
grid_settings_manager,
|
||||
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,
|
||||
order_mod_vm.algo_vm,
|
||||
attach_vm,
|
||||
_show_fills.observe_current(true)
|
||||
.Subscribe(is_visible => {
|
||||
@@ -735,13 +735,17 @@ namespace mbts.spark {
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions)) { }
|
||||
|
||||
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
_owner = owner;
|
||||
algo_vm = AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions);
|
||||
this.algo_vm = algo_vm;
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
_model.factory.subscribe_account_tuples(),
|
||||
@@ -780,8 +784,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),
|
||||
algo_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);
|
||||
|
||||
@@ -66,10 +66,6 @@ namespace mbts.spark {
|
||||
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
_vm.SubscribeDisposed(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -501,12 +501,6 @@ namespace bts.shell.pnl {
|
||||
account_name == other.account_name &&
|
||||
sod == other.sod &&
|
||||
day == other.day &&
|
||||
bid_price.Equals(other.bid_price) &&
|
||||
ask_price.Equals(other.ask_price) &&
|
||||
last_price.Equals(other.last_price) &&
|
||||
mid_price.Equals(other.mid_price) &&
|
||||
settlement_price.Equals(other.settlement_price) &&
|
||||
calc_type.Equals(other.calc_type) &&
|
||||
raw_day == other.raw_day &&
|
||||
working.summary == other.working.summary;
|
||||
|
||||
|
||||
@@ -97,22 +97,22 @@ namespace bts.shell.pnl {
|
||||
if (security.HasValue && security.Value.ContractCase != Security.ContractOneofCase.Future) {
|
||||
day_long = day_short = sod_long = sod_short = 0;
|
||||
} else {
|
||||
day_long = DoubleUtil.round_to_decimal(data.day.long_pos);
|
||||
day_short = DoubleUtil.round_to_decimal(data.day.short_pos);
|
||||
sod_long = DoubleUtil.round_to_decimal(data.sod.long_pos);
|
||||
sod_short = DoubleUtil.round_to_decimal(data.sod.short_pos);
|
||||
day_long = (decimal)DoubleUtil.nan_to_zero(data.day.long_pos);
|
||||
day_short = (decimal)DoubleUtil.nan_to_zero(data.day.short_pos);
|
||||
sod_long = (decimal)DoubleUtil.nan_to_zero(data.sod.long_pos);
|
||||
sod_short = (decimal)DoubleUtil.nan_to_zero(data.sod.short_pos);
|
||||
}
|
||||
var nd = new PNLNormalizedData(
|
||||
DoubleUtil.round_to_decimal(day_pnl.value),
|
||||
(decimal)DoubleUtil.nan_to_zero(day_pnl.value),
|
||||
day_long,
|
||||
day_short,
|
||||
DoubleUtil.round_to_decimal(data.raw_day.long_pos),
|
||||
DoubleUtil.round_to_decimal(data.raw_day.short_pos),
|
||||
DoubleUtil.round_to_decimal(sod_pnl.value),
|
||||
(decimal)DoubleUtil.nan_to_zero(data.raw_day.long_pos),
|
||||
(decimal)DoubleUtil.nan_to_zero(data.raw_day.short_pos),
|
||||
(decimal)DoubleUtil.nan_to_zero(sod_pnl.value),
|
||||
sod_long,
|
||||
sod_short,
|
||||
DoubleUtil.round_to_decimal(data.working_long_pos),
|
||||
DoubleUtil.round_to_decimal(data.working_short_pos));
|
||||
(decimal)DoubleUtil.nan_to_zero(data.working_long_pos),
|
||||
(decimal)DoubleUtil.nan_to_zero(data.working_short_pos));
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
|
||||
@@ -49,9 +49,8 @@ namespace bts.shell.pnl {
|
||||
PNLHeaderRowDisplay _header_row_display = PNLHeaderRowDisplay.Total;
|
||||
PNLRowCollectionView _rows_view;
|
||||
IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> _sorting_hierarchy;
|
||||
Color
|
||||
_grid_background_color = Colors.Transparent,
|
||||
_selection_highlight;
|
||||
Brush _selection_highlight;
|
||||
Color _grid_background_color = Colors.Transparent;
|
||||
bool _is_paused;
|
||||
// default appearances
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance>
|
||||
@@ -104,7 +103,7 @@ namespace bts.shell.pnl {
|
||||
load_from(settings);
|
||||
_dispose = Disposable.Create(
|
||||
pnl_source.Operate(
|
||||
_calculation_type.observe_current(true),
|
||||
_calculation_type.observe_current(),
|
||||
(data, calc) => data.with(calc_type: calc)
|
||||
).FilterWithRaw(filter_src, PNLRowData.key_fn, null)
|
||||
.OnDispatcher()
|
||||
@@ -176,7 +175,7 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
}
|
||||
|
||||
public Color selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
|
||||
public Brush selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
|
||||
public Color grid_background_color { get => _grid_background_color; set => Set(ref _grid_background_color, value); }
|
||||
public IValueConverter group_background_converter => _group_background_converter;
|
||||
|
||||
@@ -292,7 +291,7 @@ namespace bts.shell.pnl {
|
||||
};
|
||||
|
||||
public void load_from(PNLSettings settings) {
|
||||
selection_highlight = settings.grid.selection_highlight;
|
||||
selection_highlight = settings.grid.selection_highlight.to_brush();
|
||||
grid_background_color = settings.grid.background;
|
||||
calculation_type = settings.calculation_type;
|
||||
if (settings.filters != null) {
|
||||
|
||||
@@ -31,8 +31,6 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
|
||||
partial class PNLView : UserControl {
|
||||
DIYGridPopupManager _popup_man_backing;
|
||||
|
||||
public enum Columns {
|
||||
Account = 1,
|
||||
// Security
|
||||
@@ -242,15 +240,6 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
#endregion
|
||||
|
||||
DIYGridPopupManager _popup_man {
|
||||
get {
|
||||
if (_popup_man_backing == null) {
|
||||
_popup_man_backing = DIYGridPopupManager.create(this);
|
||||
}
|
||||
return _popup_man_backing;
|
||||
}
|
||||
}
|
||||
|
||||
void _cell_click(PNLCell cell) {
|
||||
Log.Ui.info($"PNLCell Click: {cell}.");
|
||||
try {
|
||||
@@ -368,20 +357,41 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
}
|
||||
|
||||
void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
|
||||
var view = new HeaderedContentControl {
|
||||
async void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
|
||||
// vm
|
||||
dynamic header = new ExpandoObject();
|
||||
header.context = context_desc;
|
||||
header.title = title_desc;
|
||||
header.title_foreground = fg;
|
||||
header.keep_open = false;
|
||||
// view
|
||||
var view = new PNLAuxiliaryPopupView {
|
||||
Content = content,
|
||||
Header = new AuxiliaryDataHeader {
|
||||
context = context_desc,
|
||||
title = title_desc,
|
||||
title_foreground = fg
|
||||
},
|
||||
Header = header,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Resources = new AuxiliaryDataResources()
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
_popup_man.show(view);
|
||||
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
|
||||
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
// popup adorner
|
||||
// tho unlikely, better not assume target cell is non-null
|
||||
var target_cell = _grid.ChildAtPoint<HyperlinkCell>(Mouse.GetPosition(_grid));
|
||||
var popup_adorner = new PopupAdorner(_grid, target_cell ?? (FrameworkElement)_grid, view, HorizontalPosition.Middle, VerticalPosition.Bottom, 0, 0) {
|
||||
OpenRelativeToMouse = target_cell == null,
|
||||
EnableDragMove = true,
|
||||
ClipToBounds = true
|
||||
};
|
||||
popup_adorner.SetBinding(PopupAdorner.StaysOpenProperty, new Binding("keep_open") { Source = header });
|
||||
popup_adorner.PopupElement.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
|
||||
popup_adorner.PopupElement.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => {
|
||||
Log.assert(popup_adorner != null);
|
||||
popup_adorner.detach();
|
||||
popup_adorner = null;
|
||||
}));
|
||||
popup_adorner.attach();
|
||||
popup_adorner.IsOpen = true;
|
||||
FocusManager.SetIsFocusScope(popup_adorner.PopupElement, true);
|
||||
popup_adorner.AdornedElement.Focus();
|
||||
}
|
||||
|
||||
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -7,9 +6,8 @@ using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged {
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly string _template_key;
|
||||
AlgoParamPermissions _permissions;
|
||||
bool _is_enabled, _has_value;
|
||||
T _committed_value, _value;
|
||||
@@ -17,16 +15,15 @@ namespace mbts.spark {
|
||||
public AlgoParamVM(string id, string display_name)
|
||||
: this(id, display_name, default, EqualityComparer<T>.Default) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
|
||||
public AlgoParamVM(string id, string display_name, T value)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
_value = value;
|
||||
_committed_value = value;
|
||||
_comparer = comparer;
|
||||
_template_key = template_key;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
@@ -53,9 +50,7 @@ namespace mbts.spark {
|
||||
public bool is_enabled { get => _is_enabled; set => _set(ref _is_enabled, value); }
|
||||
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
|
||||
public bool is_dirty { get; private set; }
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
|
||||
public void revert() {
|
||||
if (is_dirty) {
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_users() => _admin_model.on_users;
|
||||
public IObservable<IReadOnlyList<UserTuple>> subscribe_user_tuples() => _admin_model.on_user_tuples;
|
||||
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
|
||||
|
||||
#region Defaults
|
||||
|
||||
@@ -62,7 +62,6 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey:
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: param.AsQuantity = new bts.spark.QuantityMeasure(); break;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Reactive.Linq;
|
||||
namespace mbts.spark {
|
||||
interface IDynamicOrderParamVM {
|
||||
DynamicOrderParamDescriptor descriptor { get; }
|
||||
DynamicOrderParam param { get; }
|
||||
DynamicOrderParam param { get; set; }
|
||||
}
|
||||
|
||||
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
|
||||
@@ -21,16 +21,26 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
|
||||
DynamicOrderParam _param;
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
|
||||
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
|
||||
|
||||
private DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
this.descriptor = descriptor;
|
||||
this.param = param;
|
||||
_param = param;
|
||||
}
|
||||
|
||||
public DynamicOrderParamDescriptor descriptor { get; set; }
|
||||
public DynamicOrderParam param { get; }
|
||||
public DynamicOrderParam param {
|
||||
get => _param;
|
||||
set {
|
||||
_param = value;
|
||||
on_param_changed();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void on_param_changed();
|
||||
}
|
||||
|
||||
class DynamicOrderParamVM<TView, TModel> : DynamicOrderParamVM, IDynamicOrderParamVM<TView> {
|
||||
@@ -43,12 +53,14 @@ namespace mbts.spark {
|
||||
_field_desc = field_descriptor;
|
||||
_convert = convert;
|
||||
_convert_back = convert_back;
|
||||
value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
|
||||
}
|
||||
|
||||
public TView value {
|
||||
get => _value;
|
||||
set => Set(ref _value, value);
|
||||
set {
|
||||
_field_desc.Accessor.SetValue(param, _convert_back(_value = value));
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<TView> subscribe_value() =>
|
||||
@@ -58,6 +70,11 @@ namespace mbts.spark {
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(value);
|
||||
});
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicOrderEnumeratedVM : DynamicOrderParamVM, IDynamicOrderParamVM<string> {
|
||||
@@ -88,6 +105,11 @@ namespace mbts.spark {
|
||||
o.OnNext(selected_element);
|
||||
});
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_sync_elements();
|
||||
RaisePropertyChanged(nameof(selected_element));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
|
||||
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
|
||||
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
|
||||
@@ -126,5 +148,10 @@ namespace mbts.spark {
|
||||
o.OnNext(quantity);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
RaisePropertyChanged(nameof(quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ namespace mbts.ui.spark
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
public DataTemplate SecurityKeyTemplate { get; set; }
|
||||
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
|
||||
@@ -39,7 +38,6 @@ namespace mbts.ui.spark
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DoubleTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return IntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return SecurityKeyTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: return QuantityMeasureTemplate;
|
||||
|
||||
@@ -4,7 +4,6 @@ using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -74,9 +73,9 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<int?, int>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault(), template_key: TemplateKeys.SecurityKey);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity:
|
||||
{
|
||||
var qm = new DynamicOrderQuantityVM(descriptor);
|
||||
return attached(qm, DynamicOrderHelper.get_quantity_value, DynamicOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
|
||||
}
|
||||
@@ -89,16 +88,16 @@ namespace mbts.spark {
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
|
||||
}
|
||||
|
||||
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default, string template_key = null) {
|
||||
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default) {
|
||||
var fdesc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
|
||||
var pvm = new DynamicOrderParamVM<TView, TModel>(descriptor, fdesc, convert, convert_back);
|
||||
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value, template_key);
|
||||
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
|
||||
|
||||
TView get_from_param(DynamicOrderParam param) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
|
||||
void assign_to_param(DynamicOrderParam param, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
|
||||
}
|
||||
|
||||
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default, string template_key = null) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
|
||||
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
|
||||
add_property<T>(
|
||||
pvm.descriptor.ParamName,
|
||||
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
|
||||
@@ -106,8 +105,7 @@ namespace mbts.spark {
|
||||
() => pvm.value,
|
||||
v => pvm.value = v,
|
||||
pvm.subscribe_value(),
|
||||
default_value,
|
||||
template_key: template_key);
|
||||
default_value);
|
||||
return pvm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ namespace mbts.spark {
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
|
||||
case AlgoType.Composite: vm = composite(key, name, editing_permissions); break;
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
default: vm = generic(key, name, algo_type, editing_permissions); break;
|
||||
//default:
|
||||
// Log.Ui.debug_fatal($"Failed to create {typeof(IAlgoVM)}: unknown algo type '{algo_type}.'");
|
||||
@@ -167,8 +166,7 @@ namespace mbts.spark {
|
||||
};
|
||||
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
|
||||
RaisePropertyChanged(sd.prop_name);
|
||||
Disposable.dispose(ref def_sub);
|
||||
def_sub = new CompositeDisposable();
|
||||
Disposable.replace(ref def_sub, new CompositeDisposable());
|
||||
if (assert(new_algo != null, sd.prop_name, "No algo specified.")) {
|
||||
def_sub.Add(new_algo.subscribe_definition().Subscribe(_ => _def_change_notifier.execute()));
|
||||
if (new_algo is INotifyDataErrorInfo ndei)
|
||||
@@ -257,14 +255,14 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.dispose(ref _pending_load);
|
||||
_pending_load = Disposable.Create(
|
||||
Disposable.replace(ref _pending_load, Disposable.Create(
|
||||
// load children
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.SubscribeEach(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
// load regular props
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom)));
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
|
||||
|
||||
}
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
@@ -300,12 +298,11 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.dispose(ref _pending_load);
|
||||
_pending_load = Disposable.Create(
|
||||
Disposable.replace(ref _pending_load, Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos)));
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos))));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
// **assume that algos has more than 1 element
|
||||
@@ -545,19 +542,8 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
protected void add_property<TValue>(
|
||||
string prop_name,
|
||||
Func<TMutable, TValue> get_from_def,
|
||||
Action<TMutable, TValue> assign_to_def,
|
||||
Func<TValue> get_local,
|
||||
Action<TValue> set_local,
|
||||
IObservable<TValue> subscribe_local,
|
||||
TValue non_uniform_value = default,
|
||||
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
|
||||
string template_key = null) {
|
||||
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local(), template_key) {
|
||||
permissions = permissions
|
||||
};
|
||||
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Func<TValue> get_local, Action<TValue> set_local, IObservable<TValue> subscribe_local, TValue non_uniform_value = default, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
|
||||
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local()) { permissions = permissions };
|
||||
_algo_params.Add(param);
|
||||
_auto_props[prop_name] = new AutoProp(prop_name,
|
||||
def => {
|
||||
@@ -830,11 +816,12 @@ namespace mbts.spark {
|
||||
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
|
||||
foreach (var sd in _child_algo_selectors)
|
||||
sd.load_algo_data_recursive(algo_data, default_algo);
|
||||
Disposable.dispose(ref _selector_data_hooks);
|
||||
_selector_data_hooks = ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
|
||||
s.load_algo_data_recursive(algo_data, default_algo);
|
||||
return Disposable.empty;
|
||||
});
|
||||
Disposable.replace(
|
||||
ref _selector_data_hooks,
|
||||
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
|
||||
s.load_algo_data_recursive(algo_data, default_algo);
|
||||
return Disposable.empty;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,30 +59,23 @@ namespace mbts.spark {
|
||||
this.md_auxiliary_model = md_auxiliary_model;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders = admin_model
|
||||
.permission_filter(
|
||||
_service.subscribei_orders()
|
||||
.Naggle(interval, scheduler),
|
||||
o => o.account_id()
|
||||
).join(sstore, order => order.security_key())
|
||||
on_orders =
|
||||
_service
|
||||
.subscribei_orders()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
.join(sstore, order => order.security_key())
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
on_positions = admin_model
|
||||
.permission_filter(
|
||||
_position_service.subscribei_position_data().Naggle(interval, scheduler),
|
||||
p => p.AccountId)
|
||||
.join(sstore, pos => pos.SecurityId)
|
||||
.Table()
|
||||
.Cache();
|
||||
on_pnl = admin_model
|
||||
.permission_filter(
|
||||
_position_service.subscribei_pnl_data().Naggle(interval, scheduler),
|
||||
p => p.Pos.AccountId)
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
|
||||
on_pnl = _position_service.subscribei_pnl_data()
|
||||
.Naggle(interval, scheduler)
|
||||
.join(sstore, p => p.Pos.SecurityId)
|
||||
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable? sec, PNLUnitImmutable data)>.Default)
|
||||
.Where(t => t.has_changes())
|
||||
.Cache();
|
||||
on_account_pnl = _position_service.subscribei_pnl_account_data().Naggle(interval, scheduler).Table().Cache();
|
||||
var working_orders = on_orders.Filter(tup => tup.order.is_active());
|
||||
this.working_orders =
|
||||
on_orders.Scan(
|
||||
@@ -181,11 +174,10 @@ namespace mbts.spark {
|
||||
.Where(t => t.has_changes())
|
||||
.Cache();
|
||||
// fill tuples
|
||||
var on_fills = admin_model
|
||||
.permission_filter(
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler),
|
||||
f => f.Account)
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
.join(sstore, f => f.SecurityKey)
|
||||
.Table().Cache();
|
||||
var fill_order_tuples_join = on_fills.LeftJoin(
|
||||
@@ -273,6 +265,7 @@ namespace mbts.spark {
|
||||
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PNLUnitImmutable), int>> on_pnl { get; }
|
||||
public IObservable<TableUpdate<PNLRowData, PNLKey>> security_account_pnl { get; }
|
||||
public IObservable<TableUpdate<AccountPNLImmutable, uint>> on_account_pnl { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_positions { get; }
|
||||
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, ManualFillImmutable), int>> on_manual_fills_securities { get; }
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DiscreteValuesFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/TimeFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/ActionMenu/ActionMenu.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
|
||||
@@ -8,7 +8,6 @@ using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.spark {
|
||||
@@ -42,9 +41,6 @@ namespace bts.spark {
|
||||
SparkCommand.create("P&L (2.0)", Icons.Accounting, SparkTags.ShowPNLTag);
|
||||
|
||||
// Ladder
|
||||
public static PartialCommand<IImmutableSet<uint>> ShowLadderAutoLookup(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderAutoLookupTag, key_gesture);
|
||||
|
||||
public static PartialCommand<SecurityMD> ShowLadder(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderTag, key_gesture);
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -56,8 +55,6 @@ namespace mbts.spark {
|
||||
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
|
||||
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
|
||||
public static ITag<SecurityMD> ShowLadderTag { get; } = new SparkTag<SecurityMD>("Show Ladder");
|
||||
// TODO: get rid of the SecurityMD ShowLadderTag. This has created burdensome dependency issues.
|
||||
public static ITag<IImmutableSet<uint>> ShowLadderAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>("Show Ladder Auto Lookup");
|
||||
public static ITag<IReadOnlyList<SecurityMD>> ShowLaddersTag { get; } = new SparkTag<IReadOnlyList<SecurityMD>>(nameof(ShowLaddersTag));
|
||||
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
|
||||
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
|
||||
|
||||
@@ -70,21 +70,5 @@ namespace mbts.ui {
|
||||
}, () => Disposable.dispose(ref can_execute_sub));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public static ICommand create<T, TResult>(Action<TResult> execute, Func<T, bool> get_can_execute, Func<T, TResult> convert, out IObserver<T> observer) {
|
||||
TResult param = default;
|
||||
bool can_execute = false;
|
||||
var cmd = new DelegateCommand(() => execute(param), () => can_execute);
|
||||
IDisposable can_execute_sub = null;
|
||||
observer = new AnonymousObserver<T>(val => {
|
||||
var could_execute = can_execute;
|
||||
can_execute = get_can_execute(val);
|
||||
if (can_execute)
|
||||
param = convert(val);
|
||||
if (can_execute != could_execute)
|
||||
cmd.RaiseCanExecuteChanged();
|
||||
}, () => Disposable.dispose(ref can_execute_sub));
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ namespace mbts {
|
||||
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
|
||||
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
|
||||
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
|
||||
public static bool IsRealNonZero(this double d) => d != 0 && !double.IsNaN(d) && !double.IsInfinity(d);
|
||||
public static double CoerceNaN(double value) => CoerceNaN(value, 0.0);
|
||||
public static double CoerceNaN(double value, double if_nan) => double.IsNaN(value) ? if_nan : value;
|
||||
public static double CoerceNaNOrInfinity(double value) => CoerceNaNOrInfinity(value, 0.0);
|
||||
|
||||
@@ -131,11 +131,6 @@ namespace mbts.ui.native
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool GetCursorPos(out Win32Point pt);
|
||||
|
||||
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
|
||||
public static readonly IntPtr HWND_TOP = new IntPtr(0);
|
||||
public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwnd_insert_after, int x, int y, int cx, int cy, SWP flags);
|
||||
|
||||
@@ -260,20 +255,7 @@ namespace mbts.ui.native
|
||||
{
|
||||
SWP_NOSIZE = 1,
|
||||
SWP_NOMOVE = 2,
|
||||
SWP_NOZORDER = 0x0004,
|
||||
SWP_NOREDRAW = 0x0008,
|
||||
SWP_NOACTIVATE = 0x0010,
|
||||
// The frame changed: send WM_NCCALCSIZE
|
||||
SWP_FRAMECHANGED = 0x0020,
|
||||
SWP_SHOWWINDOW = 0x0040,
|
||||
SWP_HIDEWINDOW = 0x0080,
|
||||
SWP_NOCOPYBITS = 0x0100,
|
||||
// Don’t do owner Z ordering
|
||||
SWP_NOOWNERZORDER = 0x0200,
|
||||
// Don’t send WM_WINDOWPOSCHANGING
|
||||
SWP_NOSENDCHANGING = 0x0400,
|
||||
|
||||
TOPMOST_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING
|
||||
SWP_FRAMECHANGED = 0x20
|
||||
}
|
||||
|
||||
public enum SPI : uint
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace mbts.ui.native {
|
||||
|
||||
}
|
||||
@@ -138,7 +138,6 @@ namespace mbts.ui.native
|
||||
NCMBUTTONDOWN = 0x00A7,
|
||||
NCMBUTTONUP = 0x00A8,
|
||||
NCMOUSEMOVE = 0x00A0,
|
||||
NCMOUSELEAVE = 0x02A2,
|
||||
NCPAINT = 0x0085,
|
||||
NCRBUTTONDBLCLK = 0x00A6,
|
||||
NCRBUTTONDOWN = 0x00A4,
|
||||
|
||||
@@ -11,7 +11,6 @@ using bts.api.persistence;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reactive.Linq;
|
||||
using bts.spark;
|
||||
using bts;
|
||||
|
||||
namespace mbts.persist
|
||||
{
|
||||
@@ -147,17 +146,15 @@ namespace mbts.persist
|
||||
}
|
||||
|
||||
[Export(typeof(IUserPersist))]
|
||||
public class UserUISettings : UISettingsBase {
|
||||
public class UserUISettings : UISettingsBase
|
||||
{
|
||||
string _user;
|
||||
|
||||
public UserUISettings(uint user_id, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
_user = $"User {user_id}";
|
||||
}
|
||||
|
||||
[ImportingConstructor]
|
||||
UserUISettings(UserTuple user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: this(user.id, persist, err) { }
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
_user = $"User {user}";
|
||||
}
|
||||
|
||||
protected override string user() { return _user; }
|
||||
}
|
||||
|
||||
@@ -15,10 +15,8 @@ namespace bts.settings {
|
||||
// determine the various windows to which such rules apply.
|
||||
interface IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface ITimeAndSalesGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
|
||||
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
|
||||
static class SettingsMigrationHelper {
|
||||
public static bool apply<T>(T settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, out T new_settings) {
|
||||
|
||||
@@ -25,9 +25,7 @@ namespace mbts.persist {
|
||||
static readonly IImmutableProperty<UserSettings, GridSettings>
|
||||
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
|
||||
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
|
||||
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs)),
|
||||
_time_and_sales_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.time_and_sales_grid, (us, gs) => us.with(time_and_sales: gs));
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
|
||||
static readonly IImmutableProperty<UserSettings, PNLSettings>
|
||||
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
|
||||
@@ -38,8 +36,6 @@ namespace mbts.persist {
|
||||
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
|
||||
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
|
||||
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
|
||||
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
|
||||
readonly IEnumerable<ITimeAndSalesGridSettingsMigrationRule> _time_and_sales_migration_rules;
|
||||
readonly IUserPersist _persist;
|
||||
|
||||
UserSettings _default_settings;
|
||||
@@ -50,17 +46,13 @@ namespace mbts.persist {
|
||||
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
|
||||
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
|
||||
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
|
||||
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
|
||||
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules,
|
||||
[ImportMany]IEnumerable<ITimeAndSalesGridSettingsMigrationRule> time_and_sales_migration_rules) {
|
||||
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules) {
|
||||
//
|
||||
_persist = persist;
|
||||
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
|
||||
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
|
||||
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
|
||||
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
|
||||
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
|
||||
_time_and_sales_migration_rules = time_and_sales_migration_rules ?? Enumerable.Empty<ITimeAndSalesGridSettingsMigrationRule>();
|
||||
init();
|
||||
async void init() {
|
||||
UserSettings s;
|
||||
@@ -70,10 +62,7 @@ namespace mbts.persist {
|
||||
var wrapped_rules = new[] {
|
||||
_fills_migration_rules.merge().wrap(_fills_propi),
|
||||
_mview_migration_rules.merge().wrap(_mview_propi),
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi),
|
||||
_pnl_migration_rules.merge().wrap(_pnl_propi),
|
||||
_synth_migration_rules.merge().wrap(_synth_propi),
|
||||
_time_and_sales_migration_rules.merge().wrap(_time_and_sales_propi)
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi)
|
||||
};
|
||||
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
|
||||
// TODO: move ladder sanitization logic into ISettingsMigrationRule
|
||||
@@ -147,18 +136,6 @@ namespace mbts.persist {
|
||||
_pnl_migration_rules,
|
||||
_pnl_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_synth_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
_synth_migration_rules,
|
||||
_synth_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_time_and_sales_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
_time_and_sales_migration_rules,
|
||||
_time_and_sales_propi);
|
||||
|
||||
public async Task save_default(UserSettings value) {
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
lock (_default_settings_lock) {
|
||||
|
||||
@@ -15,8 +15,12 @@ namespace bts.utils {
|
||||
}
|
||||
|
||||
static class ObjectSnapshot {
|
||||
public static IReadOnlyList<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() =>
|
||||
PropertyGetterStore<TSource, TValue>.value;
|
||||
public static IEnumerable<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() where TSource : class where TValue : class {
|
||||
var tval = typeof(TValue);
|
||||
return typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => tval.IsAssignableFrom(p.PropertyType))
|
||||
.Select(_prop_getter<TSource, TValue>);
|
||||
}
|
||||
|
||||
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
|
||||
_property_table(instance, Array.ConvertAll(prop_names, PropertyInfoStore<T>.type.GetProperty));
|
||||
@@ -158,18 +162,6 @@ namespace bts.utils {
|
||||
}
|
||||
}
|
||||
|
||||
static class PropertyGetterStore<TSource, TValue> {
|
||||
public static readonly IReadOnlyList<Func<TSource, TValue>> value;
|
||||
|
||||
static PropertyGetterStore() {
|
||||
var tval = typeof(TValue);
|
||||
value = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => tval.IsAssignableFrom(p.PropertyType))
|
||||
.Select(_prop_getter<TSource, TValue>)
|
||||
.ToReadOnlyList();
|
||||
}
|
||||
}
|
||||
|
||||
class PropNode {
|
||||
public readonly PropertyInfo prop;
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct SyntheticTuple : IEquatable<SyntheticTuple>, IComparable<SyntheticTuple>, IComparable {
|
||||
public static readonly Func<SyntheticTuple, uint> key_fn = a => a.security.Id;
|
||||
public static readonly Func<SyntheticTuple?, uint> nullable_key_fn = a => a.HasValue ? a.Value.security.Id : 0u;
|
||||
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly string user_name;
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
public readonly int? num_working_orders;
|
||||
|
||||
public SyntheticTuple(SecurityImmutable security, string user_name, ExecutionStrategyImmutable? strategy, int? num_working_orders = null) {
|
||||
this.security = security;
|
||||
this.user_name = user_name;
|
||||
this.strategy = strategy;
|
||||
this.num_working_orders = num_working_orders;
|
||||
}
|
||||
|
||||
public int CompareTo(SyntheticTuple other) =>
|
||||
SecurityHelper.Comparer.Compare(security, other.security);
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is SyntheticTuple other ? CompareTo(other) : 1;
|
||||
|
||||
public bool Equals(SyntheticTuple other) =>
|
||||
security == other.security &&
|
||||
user_name == other.user_name &&
|
||||
EqualityComparer.nullable_equals(strategy, other.strategy);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security.Id.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
user_name == null ? security.ToString() : $"{user_name}:{security}";
|
||||
|
||||
public static bool operator ==(SyntheticTuple a, SyntheticTuple b) => a.Equals(b);
|
||||
public static bool operator !=(SyntheticTuple a, SyntheticTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
public readonly struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
|
||||
const string _admin_name = "bts";
|
||||
|
||||
struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
|
||||
public static readonly Func<UserTuple, uint> key_fn = u => u.id;
|
||||
public static UserTuple admin => new UserTuple(0, _admin_name, Permissions.Admin);
|
||||
|
||||
public readonly uint id;
|
||||
public readonly string name;
|
||||
public readonly Permissions permissions;
|
||||
|
||||
public UserTuple(uint id, string name)
|
||||
: this(id, name, name == _admin_name ? Permissions.Admin : Permissions.Regular) { }
|
||||
|
||||
public UserTuple(uint id, string name, Permissions permissions) {
|
||||
public UserTuple(uint id, string name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public enum Permissions {
|
||||
Regular,
|
||||
Admin
|
||||
}
|
||||
|
||||
public int CompareTo(UserTuple other) {
|
||||
@@ -41,9 +28,4 @@ namespace bts {
|
||||
public static bool operator ==(UserTuple a, UserTuple b) => a.Equals(b);
|
||||
public static bool operator !=(UserTuple a, UserTuple b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
public static class UserTupleExtensions {
|
||||
public static bool can_access_user_data(this UserTuple user, uint user_id) =>
|
||||
user.id == user_id || user.permissions == UserTuple.Permissions.Admin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,9 @@
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
>
|
||||
<!--Control Base Colors-->
|
||||
<Color x:Key="Control.SuperLightGrayColor">#F6F6F6</Color>
|
||||
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
|
||||
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
|
||||
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
|
||||
<Color x:Key="Control.MediumDarkGrayColor">#ACACAC</Color>
|
||||
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
|
||||
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
|
||||
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
|
||||
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
|
||||
<Color x:Key="Control.DarkMediumGrayColor">#FF959595</Color>
|
||||
<Color x:Key="Control.DarkGrayColor">#707070</Color>
|
||||
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
|
||||
|
||||
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
|
||||
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
|
||||
@@ -144,4 +130,4 @@
|
||||
<SolidColorBrush x:Key="ListBoxItem.Selected.Inactive.Border" Color="{StaticResource ListBoxItem.Selected.Inactive.Border.Color}"/>
|
||||
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Background" Color="{StaticResource ListBoxItem.MouseOver.Background.Color}"/>
|
||||
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Border" Color="{StaticResource ListBoxItem.MouseOver.Border.Color}"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -1,102 +1,78 @@
|
||||
<ResourceDictionary
|
||||
<c:PopupControl
|
||||
x:Class="mbts.ui.controls.ActionMenu"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
Margin="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Padding="2"
|
||||
PopupPlacement="Bottom"
|
||||
ShowExpanderArrow="False"
|
||||
Cursor="Hand"
|
||||
>
|
||||
<SolidColorBrush x:Key="SeparatorFill" Color="{DynamicResource Control.AlphaMediumGrayColor}"/>
|
||||
|
||||
<ControlTemplate x:Key="ActionMenuTemplate" TargetType="c:ActionMenu">
|
||||
<Border
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{DynamicResource Button.MouseOver.Storyboard}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<c:PopupControl.Resources>
|
||||
<CollectionViewSource x:Key="ItemsSourceView" Source="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=c:ActionMenu}}">
|
||||
<CollectionViewSource.GroupDescriptions>
|
||||
<PropertyGroupDescription PropertyName="group"/>
|
||||
</CollectionViewSource.GroupDescriptions>
|
||||
</CollectionViewSource>
|
||||
</c:PopupControl.Resources>
|
||||
<c:PopupControl.Header>
|
||||
<Border Width="16">
|
||||
<Grid>
|
||||
<Button
|
||||
x:Name="button"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Padding="2.5,2"
|
||||
Style="{DynamicResource SparkToolBarButton}"
|
||||
>
|
||||
<Button.Background>
|
||||
<SolidColorBrush/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<Popup c:PopupViewController.ButtonSource="{Binding ElementName=button}" Placement="Bottom" PlacementTarget="{Binding ElementName=button}" StaysOpen="False">
|
||||
<Border Background="{DynamicResource Control.LightGrayBrush}" BorderThickness="1">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource Control.DarkMediumGrayColor}"/>
|
||||
</Border.BorderBrush>
|
||||
<ItemsPresenter/>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.Row="0" Fill="Black" Height="2" HorizontalAlignment="Stretch"/>
|
||||
<Rectangle Grid.Row="1" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
|
||||
<Rectangle Grid.Row="2" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<DataTemplate x:Key="HamburgerButtonTemplate">
|
||||
<Image
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Source="{x:Static u:Icons.HamburgerMenu}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="ActionMenuBase" TargetType="c:ActionMenu">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template" Value="{StaticResource ActionMenuTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type u:MenuItemVM}">
|
||||
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
|
||||
<Style TargetType="c:ActionMenu" BasedOn="{StaticResource ActionMenuBase}">
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
<Setter Property="Margin" Value="1"/>
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource HamburgerButtonTemplate}"/>
|
||||
<Setter Property="Width" Value="22"/>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="MinWidth" Value="167"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Header" Value="{Binding name}"/>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={x:Static cv:ImageSourceIconConverter.Instance}}"/>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:PopupControl.Header>
|
||||
<Menu ItemsSource="{Binding Source={StaticResource ItemsSourceView}}">
|
||||
<Menu.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Menu.ItemsPanel>
|
||||
<Menu.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="GroupItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<StackPanel>
|
||||
<ItemsPresenter/>
|
||||
<Rectangle HorizontalAlignment="Stretch" Height="1" Margin="22,0,0,0" Fill="LightGray"/>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</Menu.GroupStyle>
|
||||
<Menu.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
|
||||
<!--NOTE: CommandParameter binding must be declared BEFORE Command,
|
||||
otherwise, CanExecuted is called before the parameter value is realized...I know, it sucks...-->
|
||||
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Header" Value="{Binding name}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||
<Setter Property="MinWidth" Value="167"/>
|
||||
</Style>
|
||||
</Menu.ItemContainerStyle>
|
||||
</Menu>
|
||||
</c:PopupControl>
|
||||
@@ -1,15 +1,22 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for ActionMenu.xaml
|
||||
/// </summary>
|
||||
class ActionMenu : HeaderedItemsControl {
|
||||
static ActionMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionMenu), new FrameworkPropertyMetadata(typeof(ActionMenu)));
|
||||
partial class ActionMenu : PopupControl {
|
||||
public ActionMenu() =>
|
||||
InitializeComponent();
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() =>
|
||||
new MenuItem();
|
||||
public static readonly DependencyProperty ItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ActionMenu>(
|
||||
nameof(ItemsSource), Enumerable.Empty<IMenuItemVM>());
|
||||
|
||||
public IEnumerable ItemsSource {
|
||||
get => GetValue(ItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
class AdornerHost : Adorner {
|
||||
UIElement _child;
|
||||
|
||||
public AdornerHost(UIElement adorned_element)
|
||||
: base(adorned_element) { }
|
||||
|
||||
public static readonly DependencyProperty ChildProperty =
|
||||
WPFHelper.create_dp<UIElement, AdornerHost>(
|
||||
nameof(Child), (o, v) => {
|
||||
if (o._child != null)
|
||||
o.RemoveVisualChild(o._child);
|
||||
o._child = v;
|
||||
if (v != null)
|
||||
o.AddVisualChild(v);
|
||||
});
|
||||
|
||||
public UIElement Child {
|
||||
get => _child;
|
||||
set => SetValue(ChildProperty, value);
|
||||
}
|
||||
|
||||
protected override int VisualChildrenCount => _child != null ? 1 : 0;
|
||||
|
||||
protected override Visual GetVisualChild(int index) =>
|
||||
index == 0 && _child != null ? _child : throw new ArgumentOutOfRangeException();
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize) {
|
||||
if (_child == null) {
|
||||
return base.ArrangeOverride(finalSize);
|
||||
}
|
||||
_child.Arrange(new Rect(finalSize));
|
||||
return _child.DesiredSize;
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size constraint) {
|
||||
if (_child == null) {
|
||||
return base.MeasureOverride(constraint);
|
||||
}
|
||||
_child.Measure(constraint);
|
||||
return _child.DesiredSize;
|
||||
}
|
||||
}
|
||||
|
||||
class AdornerHost<TChild> : AdornerHost where TChild : UIElement {
|
||||
public AdornerHost(UIElement adorned_element)
|
||||
: base(adorned_element) { }
|
||||
|
||||
public new TChild Child {
|
||||
get => base.Child as TChild;
|
||||
set => base.Child = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
class DIYGridCellHoverAdorner : Adorner<ListBox>, IDisposable {
|
||||
Brush _row_fill, _cell_fill;
|
||||
Pen _row_stroke, _cell_stroke;
|
||||
|
||||
DIYGridRowsPresenter _rows_presenter;
|
||||
ScrollViewer _scroll_viewer;
|
||||
ScrollContentPresenter _scroll_content_backing;
|
||||
DIYGrid _grid;
|
||||
IDisposable _dispose;
|
||||
DIYGrid.CellGeometry? _hover_cell;
|
||||
AdornerHost _menu_host = null;
|
||||
ActionMenu _menu => _menu_host.Child as ActionMenu;
|
||||
|
||||
#region RowFill
|
||||
public static readonly DependencyProperty RowFillProperty =
|
||||
WPFHelper.create_dp<Brush, DIYGridCellHoverAdorner>(
|
||||
nameof(RowFill),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._row_fill = v,
|
||||
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
|
||||
|
||||
public SolidColorBrush RowFill {
|
||||
get => GetValue(RowFillProperty) as SolidColorBrush;
|
||||
set => SetValue(RowFillProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RowStroke
|
||||
public static readonly DependencyProperty RowStrokeProperty =
|
||||
WPFHelper.create_dp<Pen, DIYGridCellHoverAdorner>(
|
||||
nameof(RowStroke),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._row_stroke = v);
|
||||
|
||||
public Pen RowStroke {
|
||||
get => GetValue(RowStrokeProperty) as Pen;
|
||||
set => SetValue(RowStrokeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CellFill
|
||||
public static readonly DependencyProperty CellFillProperty =
|
||||
WPFHelper.create_dp<Brush, DIYGridCellHoverAdorner>(
|
||||
nameof(CellFill),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._cell_fill = v);
|
||||
|
||||
public SolidColorBrush CellFill {
|
||||
get => GetValue(CellFillProperty) as SolidColorBrush;
|
||||
set => SetValue(CellFillProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CellStroke
|
||||
public static readonly DependencyProperty CellStrokeProperty =
|
||||
WPFHelper.create_dp<Pen, DIYGridCellHoverAdorner>(
|
||||
nameof(CellStroke),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._cell_stroke = v);
|
||||
|
||||
public Pen CellStroke {
|
||||
get => GetValue(CellStrokeProperty) as Pen;
|
||||
set => SetValue(CellStrokeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public DIYGridCellHoverAdorner(DIYGridRowsPresenter adorned_element, ScrollViewer scroll_viewer)
|
||||
: base(adorned_element) {
|
||||
_row_fill = RowFill;
|
||||
_row_stroke = RowStroke;
|
||||
_cell_fill = CellFill;
|
||||
_cell_stroke = CellStroke;
|
||||
_scroll_viewer = scroll_viewer;
|
||||
IsHitTestVisible = false;
|
||||
_rows_presenter = adorned_element;
|
||||
|
||||
_grid = _rows_presenter.find_ancestor_of_type<DIYGrid>();
|
||||
_dispose = Disposable.Create(
|
||||
_grid.SubscribeMouseHoverCell().Subscribe(cell => {
|
||||
_hover_cell = cell;
|
||||
if (_hover_cell.HasValue) {
|
||||
var (row, col) = _hover_cell.Value;
|
||||
var row_tl = row.TranslatePoint(default, this);
|
||||
}
|
||||
InvalidateVisual();
|
||||
}));
|
||||
}
|
||||
|
||||
ScrollContentPresenter _scroll_content =>
|
||||
_scroll_content_backing ?? (_scroll_content_backing = _scroll_viewer.descendants_of_type<ScrollContentPresenter>().FirstOrDefault());
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext) {
|
||||
base.OnRender(drawingContext);
|
||||
if (_hover_cell.HasValue) {
|
||||
(var row, var cell) = _hover_cell.Value;
|
||||
if (row != null) {
|
||||
var row_tl = new Lazy<Point>(() => row.TranslatePoint(default, this));
|
||||
if (_row_fill != null || _row_stroke != null) {
|
||||
var row_rect = new Rect(row_tl.Value, row.RenderSize);
|
||||
if (_scroll_content != null) {
|
||||
var content_bounds = this.GetChildBounds(_scroll_content);
|
||||
row_rect.Intersect(content_bounds);
|
||||
}
|
||||
drawingContext.DrawRectangle(_row_fill, _row_stroke, row_rect, true);
|
||||
}
|
||||
if (_cell_fill != null || _cell_stroke != null) {
|
||||
var cell_rect = cell.to_visible_rect(row.ActualHeight, row_tl.Value.Y);
|
||||
drawingContext.DrawRectangle(_cell_fill, _cell_stroke, cell_rect, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace mbts.ui.controls.adorners {
|
||||
nameof(Fill),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._fill = v,
|
||||
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
|
||||
DIYGrid.DefaultSelectionHighlight);
|
||||
|
||||
public SolidColorBrush Fill {
|
||||
get => GetValue(FillProperty) as SolidColorBrush;
|
||||
@@ -38,7 +38,7 @@ namespace mbts.ui.controls.adorners {
|
||||
nameof(Stroke),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._stroke = v,
|
||||
FrozenPenCache.get_pen(DIYGrid.DefaultSelectionHighlight));
|
||||
DIYGrid.DefaultSelectionStroke);
|
||||
|
||||
public Pen Stroke {
|
||||
get => GetValue(StrokeProperty) as Pen;
|
||||
|
||||
@@ -8,8 +8,8 @@ using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
public class DragRectAdorner : Adorner, IDisposable {
|
||||
Brush _fill;
|
||||
Pen _stroke;
|
||||
Brush _fill = DIYGrid.DefaultSelectionHighlight;
|
||||
Pen _stroke = DIYGrid.DefaultSelectionStroke;
|
||||
readonly ScrollViewerTuple _scroll_viewer_tup;
|
||||
readonly double _drift_multiplier;
|
||||
Point? _drag_start;
|
||||
@@ -20,7 +20,7 @@ namespace mbts.ui.controls.adorners {
|
||||
nameof(Fill),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._fill = v,
|
||||
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
|
||||
DIYGrid.DefaultSelectionHighlight);
|
||||
|
||||
public SolidColorBrush Fill {
|
||||
get => GetValue(FillProperty) as SolidColorBrush;
|
||||
@@ -32,7 +32,7 @@ namespace mbts.ui.controls.adorners {
|
||||
nameof(Stroke),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._stroke = v,
|
||||
FrozenPenCache.get_pen(DIYGrid.DefaultSelectionHighlight));
|
||||
DIYGrid.DefaultSelectionStroke);
|
||||
|
||||
public Pen Stroke {
|
||||
get => GetValue(StrokeProperty) as Pen;
|
||||
@@ -48,8 +48,6 @@ namespace mbts.ui.controls.adorners {
|
||||
/// <param name="scroll_viewer">The element's ScrollViewer.</param>
|
||||
/// <param name="drift_multiplier">The number of pixels per scroll unit.</param>
|
||||
public DragRectAdorner(FrameworkElement adorned_element, ScrollViewer scroll_viewer = null, double drift_multiplier = 1) : base(adorned_element) {
|
||||
_fill = Fill;
|
||||
_stroke = Stroke;
|
||||
_drift_multiplier = drift_multiplier;
|
||||
AdornedElement.PreviewMouseDown += _on_preview_mouse_down;
|
||||
AdornedElement.PreviewMouseMove += _on_preview_mouse_move;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using bts.utils;
|
||||
using mbts.ui.extensions;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
class RowSelectionAdorner : Adorner<ListBox>, IDisposable {
|
||||
Brush _fill;
|
||||
Pen _stroke;
|
||||
public class RowSelectionAdorner : Adorner, DIYGrid.ISelectionHighlightListener, IDisposable {
|
||||
Brush _fill = DIYGrid.DefaultSelectionHighlight;
|
||||
Pen _stroke = DIYGrid.DefaultSelectionStroke;
|
||||
|
||||
DIYGridRowsPresenter _diy_rp;
|
||||
ScrollViewer _diy_scroll;
|
||||
@@ -23,21 +25,20 @@ namespace mbts.ui.controls.adorners {
|
||||
nameof(Fill),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._fill = v,
|
||||
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
|
||||
DIYGrid.DefaultSelectionHighlight);
|
||||
|
||||
public SolidColorBrush Fill {
|
||||
get => GetValue(FillProperty) as SolidColorBrush;
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Stroke
|
||||
public static readonly DependencyProperty StrokeProperty =
|
||||
WPFHelper.create_dp<Pen, RowSelectionAdorner>(
|
||||
nameof(Stroke),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._stroke = v,
|
||||
FrozenPenCache.get_pen(DIYGrid.DefaultSelectionHighlight.with(255)));
|
||||
DIYGrid.DefaultSelectionStroke);
|
||||
|
||||
public Pen Stroke {
|
||||
get => GetValue(StrokeProperty) as Pen;
|
||||
@@ -45,9 +46,7 @@ namespace mbts.ui.controls.adorners {
|
||||
}
|
||||
#endregion
|
||||
|
||||
public RowSelectionAdorner(DIYGridRowsPresenter adorned_element, ScrollViewer scroll_viewer) : base(adorned_element) {
|
||||
_fill = Fill;
|
||||
_stroke = Stroke;
|
||||
internal RowSelectionAdorner(DIYGridRowsPresenter adorned_element, ScrollViewer scroll_viewer) : base(adorned_element) {
|
||||
// we want our clicks to go through to the underlying grid
|
||||
IsHitTestVisible = false;
|
||||
|
||||
@@ -61,6 +60,7 @@ namespace mbts.ui.controls.adorners {
|
||||
_disposables.Add(_diy_scroll.SubscribeScrollChanged((s, e) => InvalidateVisual()));
|
||||
}
|
||||
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext) {
|
||||
base.OnRender(drawingContext);
|
||||
if (AdornedElement is ListBox lb && lb.SelectedItems != null && lb.SelectedItems.Count > 0) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
@@ -11,7 +10,6 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using bts;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
@@ -85,39 +83,79 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
|
||||
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
|
||||
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
|
||||
|
||||
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
|
||||
if (content_width > width || unclipped_left < left)
|
||||
return new Rect(left, 0, width - left + unclipped_left, row_height);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IDisposable draw_with_clip(DrawingContext dc, double raw_content_width, double raw_content_height, Thickness padding, double row_height, ColumnGeometry position) =>
|
||||
draw_with_clip(dc,
|
||||
raw_content_width + padding.Left + padding.Right,
|
||||
raw_content_height + padding.Top + padding.Bottom,
|
||||
row_height, position.left, position.unclipped_left, position.width);
|
||||
|
||||
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
|
||||
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
|
||||
if (clip.HasValue) {
|
||||
dc.PushClip(new RectangleGeometry(clip.Value));
|
||||
return Disposable.Create(dc.Pop);
|
||||
} else return Disposable.Create(() => { });
|
||||
}
|
||||
|
||||
public static void draw_text(
|
||||
DrawingContext dc,
|
||||
GlyphContext glyph_context,
|
||||
double font_size,
|
||||
string text,
|
||||
ColumnGeometry position,
|
||||
double row_height,
|
||||
Brush foreground,
|
||||
Brush background,
|
||||
HorizontalAlignment x_align,
|
||||
VerticalAlignment y_align,
|
||||
Thickness padding,
|
||||
bool round_to_layout = true) {
|
||||
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
|
||||
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
|
||||
//
|
||||
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
|
||||
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
|
||||
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
|
||||
if (!string.IsNullOrEmpty(text)) {
|
||||
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
|
||||
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
|
||||
}
|
||||
}
|
||||
|
||||
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
|
||||
if (glyph_run == null && background == null)
|
||||
return;
|
||||
var rect = position.to_rect(row_height);
|
||||
if (background != null) {
|
||||
var guidelines = rect.AntiAliasGuidelines(0);
|
||||
dc.PushGuidelineSet(guidelines);
|
||||
dc.DrawRectangle(background, null, rect);
|
||||
dc.Pop();
|
||||
}
|
||||
if (glyph_run != null) {
|
||||
dc.PushClip(new RectangleGeometry(rect));
|
||||
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
|
||||
if (background != null)
|
||||
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
|
||||
dc.DrawGlyphRun(foreground, glyph_run);
|
||||
dc.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void draw_text(
|
||||
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
|
||||
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
|
||||
//
|
||||
using (position.clip_draw(dc, row_height)) {
|
||||
double adjusted_left = position.unclipped_left;
|
||||
double available_width = position.width;
|
||||
if (background != null)
|
||||
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
|
||||
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
|
||||
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
|
||||
if (trigger_actions != null) {
|
||||
foreach (CellAnimation ta in trigger_actions) {
|
||||
switch (ta.animation_type_case) {
|
||||
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
|
||||
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
|
||||
break;
|
||||
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
|
||||
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
|
||||
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
|
||||
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
|
||||
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var (brush, glyph) in glyphs) {
|
||||
dc.DrawGlyphRun(brush, glyph);
|
||||
}
|
||||
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
|
||||
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +165,14 @@ namespace mbts.ui.controls {
|
||||
public static void change_position(FrameworkElement cell_view, double row_height, ColumnGeometry? position) {
|
||||
if (position.HasValue) {
|
||||
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Visible);
|
||||
var clip = get_clip(cell_view.DesiredSize, row_height, 0, position.Value.unclipped_left - position.Value.left, position.Value.width);
|
||||
var el_clip = cell_view.Clip as RectangleGeometry;
|
||||
if (clip.HasValue) {
|
||||
if (el_clip == null || !el_clip.Rect.Equals(clip.Value))
|
||||
cell_view.Clip = new RectangleGeometry(clip.Value);
|
||||
} else if (cell_view.Clip != null) {
|
||||
cell_view.Clip = null;
|
||||
}
|
||||
} else {
|
||||
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Collapsed);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
enum HyperlinkCellClickMode {
|
||||
Text,
|
||||
Anywhere
|
||||
}
|
||||
|
||||
class HyperlinkCell : FrameworkElement, ICommandSource {
|
||||
// these fields each represent properties that are accessed frequently,
|
||||
// they are stored locally to reduce repetative DependencyProperty lookups
|
||||
@@ -30,35 +18,10 @@ namespace mbts.ui.controls {
|
||||
Brush _background, _foreground_backing, _border_brush_backing;
|
||||
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
|
||||
GlyphRun _glyph_run => _gcr.glyph_run;
|
||||
bool
|
||||
_is_mouse_over_text,
|
||||
_is_mouse_over_click_area_backing,
|
||||
_can_execute,
|
||||
_snaps_to_device_pixels;
|
||||
bool _is_mouse_over_text, _can_execute;
|
||||
Pen _underline_pen_backing, _border_stroke_pen_backing;
|
||||
GlyphContentRect _gcr = new GlyphContentRect {
|
||||
// add 2 pixels to account for underline (1 for space, 1 for the line itself)
|
||||
content_height_offset = HighDpiExtensions.pixel_unit * 2
|
||||
};
|
||||
IDisposable _mouse_over_click_area_op;
|
||||
GlyphContentRect _gcr = new GlyphContentRect();
|
||||
|
||||
bool _is_mouse_over_click_area {
|
||||
get => _is_mouse_over_click_area_backing;
|
||||
set {
|
||||
if (_is_mouse_over_click_area_backing != value) {
|
||||
Disposable.dispose(ref _mouse_over_click_area_op);
|
||||
_is_mouse_over_click_area_backing = value;
|
||||
if (value) {
|
||||
_mouse_over_click_area_op = WorkLoop.Start(
|
||||
$"Evaluate {nameof(HyperlinkCell)} {nameof(IsMouseOverClickArea)}",
|
||||
TimeSpan.FromMilliseconds(10),
|
||||
DispatcherPriority.Input,
|
||||
_on_mouse_changed,
|
||||
WorkLoop.default_excessive_cycle_ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Brush _foreground {
|
||||
get => _foreground_backing;
|
||||
set {
|
||||
@@ -74,23 +37,13 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
Thickness _border_thickness {
|
||||
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
|
||||
get => _gcr.border_thickness;
|
||||
set {
|
||||
_gcr.border_thickness = new Thickness(value.Left);
|
||||
_border_stroke_pen_backing = null;
|
||||
}
|
||||
}
|
||||
Pen _underline_pen {
|
||||
get {
|
||||
if (_underline_pen_backing == null) {
|
||||
var fg = _foreground;
|
||||
if (fg != null) {
|
||||
_underline_pen_backing = new Pen(fg, _snaps_to_device_pixels ? HighDpiExtensions.pixel_unit : 1);
|
||||
}
|
||||
}
|
||||
return _underline_pen_backing;
|
||||
}
|
||||
}
|
||||
Pen _underline_pen => _underline_pen_backing ?? (_foreground == null ? null : (_underline_pen_backing = new Pen(_foreground, 1)));
|
||||
Pen _border_stroke_pen =>
|
||||
// border_thickness.Left is used as the uniform border thickness. This can be changed if need be.
|
||||
_border_stroke_pen_backing == null && _border_brush != null && _border_thickness.Left > 0 ?
|
||||
@@ -98,8 +51,6 @@ namespace mbts.ui.controls {
|
||||
_border_stroke_pen_backing;
|
||||
|
||||
static HyperlinkCell() {
|
||||
IsMouseOverClickAreaProperty = IsMouseOverClickAreaPropertyKey.DependencyProperty;
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkCell), new FrameworkPropertyMetadata(typeof(HyperlinkCell)));
|
||||
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
|
||||
HorizontalAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
@@ -113,11 +64,6 @@ namespace mbts.ui.controls {
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.width = v,
|
||||
double.NaN);
|
||||
WPFHelper.override_metadata<bool, HyperlinkCell>(
|
||||
SnapsToDevicePixelsProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._snaps_to_device_pixels = v,
|
||||
true);
|
||||
}
|
||||
|
||||
public HyperlinkCell() {
|
||||
@@ -127,7 +73,7 @@ namespace mbts.ui.controls {
|
||||
_gcr.min_height = MinHeight;
|
||||
_gcr.height = Height;
|
||||
_gcr.max_height = Height;
|
||||
_gcr.padding = Padding;
|
||||
_gcr.padding = _underline_padding(Padding);
|
||||
_gcr.border_thickness = BorderThickness;
|
||||
_gcr.horizontal_alignment = HorizontalAlignment;
|
||||
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
|
||||
@@ -140,39 +86,8 @@ namespace mbts.ui.controls {
|
||||
if (Text is string str && !string.IsNullOrEmpty(str))
|
||||
_gcr.text_lines = new[] { str };
|
||||
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
|
||||
_snaps_to_device_pixels = SnapsToDevicePixels;
|
||||
}
|
||||
|
||||
#region IsMouseOverClickArea
|
||||
static readonly DependencyPropertyKey IsMouseOverClickAreaPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(
|
||||
"IsMouseOverClickArea",
|
||||
typeof(bool),
|
||||
typeof(HyperlinkCell),
|
||||
new FrameworkPropertyMetadata(
|
||||
false,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
OnIsMouseOverClickAreaChanged,
|
||||
CoerceIsMouseOverClickArea));
|
||||
|
||||
public static readonly DependencyProperty IsMouseOverClickAreaProperty;
|
||||
|
||||
static void OnIsMouseOverClickAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is HyperlinkCell hlc)
|
||||
hlc._is_mouse_over_click_area = e.NewValue is bool b && b;
|
||||
}
|
||||
|
||||
static object CoerceIsMouseOverClickArea(DependencyObject d, object baseValue) =>
|
||||
d is HyperlinkCell hlc ?
|
||||
hlc._is_mouse_over_text || (hlc.IsMouseOver && hlc.HyperlinkClickMode == HyperlinkCellClickMode.Anywhere) :
|
||||
baseValue;
|
||||
|
||||
public bool IsMouseOverClickArea {
|
||||
get => (bool)GetValue(IsMouseOverClickAreaProperty);
|
||||
private set => SetValue(IsMouseOverClickAreaPropertyKey, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BorderThickness
|
||||
public static readonly DependencyProperty BorderThicknessProperty =
|
||||
Control.BorderThicknessProperty.AddOwner(typeof(HyperlinkCell),
|
||||
@@ -220,8 +135,7 @@ namespace mbts.ui.controls {
|
||||
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
|
||||
new FrameworkPropertyMetadata(
|
||||
VerticalAlignment.Center,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange,
|
||||
(d, e) => ((HyperlinkCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
|
||||
|
||||
public VerticalAlignment VerticalContentAlignment {
|
||||
@@ -301,7 +215,7 @@ namespace mbts.ui.controls {
|
||||
nameof(Padding),
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(p, v) => p._gcr.padding = default, default);
|
||||
(p, v) => p._gcr.padding = _underline_padding(v), DIYGrid.DefaultCellPadding);
|
||||
|
||||
public Thickness Padding { get => (Thickness)GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); }
|
||||
#endregion
|
||||
@@ -317,18 +231,6 @@ namespace mbts.ui.controls {
|
||||
public string Text { get => GetValue(TextProperty) as string; set => SetValue(TextProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region HyperlinkClickMode
|
||||
public static readonly DependencyProperty HyperlinkClickModeProperty =
|
||||
WPFHelper.create_dp<HyperlinkCellClickMode, HyperlinkCell>(
|
||||
nameof(HyperlinkClickMode),
|
||||
(o, v) => o.CoerceValue(IsMouseOverClickAreaProperty));
|
||||
|
||||
public HyperlinkCellClickMode HyperlinkClickMode {
|
||||
get => (HyperlinkCellClickMode)GetValue(HyperlinkClickModeProperty);
|
||||
set => SetValue(HyperlinkClickModeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ICommandSource
|
||||
|
||||
#region Command
|
||||
@@ -395,8 +297,8 @@ namespace mbts.ui.controls {
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
_on_mouse_changed(e);
|
||||
if (_is_mouse_over_click_area) {
|
||||
_check_mouse_over_text(e);
|
||||
if (_is_mouse_over_text) {
|
||||
// Hyperlink should take focus when left mouse button is clicked on it
|
||||
// This is consistent with all ButtonBase controls and current Win32 behavior
|
||||
Focus();
|
||||
@@ -406,8 +308,6 @@ namespace mbts.ui.controls {
|
||||
if (e.ButtonState == MouseButtonState.Pressed) {
|
||||
// Capture the mouse, and make sure we got it.
|
||||
CaptureMouse();
|
||||
} else {
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -415,25 +315,23 @@ namespace mbts.ui.controls {
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
_on_mouse_changed(e);
|
||||
if (IsMouseCaptured) {
|
||||
_check_mouse_over_text(e);
|
||||
if (IsMouseCaptured)
|
||||
ReleaseMouseCapture();
|
||||
if (_can_execute)
|
||||
CommandHelper.ExecuteCommandSource(this);
|
||||
_on_mouse_changed(e);
|
||||
e.Handled = true;
|
||||
}
|
||||
if (_can_execute && _is_mouse_over_text)
|
||||
CommandHelper.ExecuteCommandSource(this);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) => _on_mouse_changed();
|
||||
protected override void OnPreviewMouseMove(MouseEventArgs e) {
|
||||
_on_mouse_changed(e);
|
||||
e.Handled = IsMouseCaptured;
|
||||
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) {
|
||||
if (_is_mouse_over_text != IsMouseDirectlyOver)
|
||||
_toggle_mouse_over_text();
|
||||
}
|
||||
protected override void OnMouseEnter(MouseEventArgs e) => _on_mouse_changed(e);
|
||||
protected override void OnMouseLeave(MouseEventArgs e) => _on_mouse_changed(e);
|
||||
protected override void OnPreviewMouseMove(MouseEventArgs e) => _check_mouse_over_text(e);
|
||||
protected override void OnMouseEnter(MouseEventArgs e) => _check_mouse_over_text(e);
|
||||
protected override void OnMouseLeave(MouseEventArgs e) => _check_mouse_over_text(e);
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize) => finalSize;
|
||||
|
||||
@@ -449,32 +347,45 @@ namespace mbts.ui.controls {
|
||||
var rs = RenderSize;
|
||||
var rect = new Rect(0, 0, rs.Width, rs.Height);
|
||||
var bg = Background;
|
||||
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
|
||||
var gr = _glyph_run;
|
||||
if (gr != null) {
|
||||
if (bg != null) {
|
||||
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
|
||||
}
|
||||
if (_glyph_run != null) {
|
||||
drawingContext.PushClip(new RectangleGeometry(rect));
|
||||
drawingContext.DrawGlyphRun(_foreground, gr);
|
||||
if (IsMouseOverClickArea) {
|
||||
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
|
||||
if (_is_mouse_over_text) {
|
||||
// draw underline
|
||||
var ul_pen = _underline_pen;
|
||||
if (ul_pen != null) {
|
||||
var line_start = new Point(gr.BaselineOrigin.X, gr.BaselineOrigin.Y + 2 * ul_pen.Thickness);
|
||||
drawingContext.DrawHorizontalLine(ul_pen, line_start, gr.AdvanceWidths.Sum(), _snaps_to_device_pixels);
|
||||
}
|
||||
double x = _gcr.glyph_run.BaselineOrigin.X;
|
||||
double y = _gcr.glyph_run.BaselineOrigin.Y + 2;
|
||||
drawingContext.DrawLine(_underline_pen, new Point(x, y), new Point(x + _gcr.glyph_run.AdvanceWidths.Sum(), y));
|
||||
}
|
||||
drawingContext.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
void _on_mouse_changed() =>
|
||||
_on_mouse_changed(Mouse.GetPosition(this));
|
||||
static Thickness _underline_padding(Thickness padding) =>
|
||||
new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + 2);
|
||||
|
||||
void _on_mouse_changed(MouseEventArgs e) =>
|
||||
_on_mouse_changed(e.GetPosition(this));
|
||||
void _check_mouse_over_text(MouseEventArgs e) {
|
||||
var pt = e.GetPosition(this);
|
||||
var orig = _gcr.content_area;
|
||||
var exp = orig.Expanded(1);
|
||||
if (_is_mouse_over_text != exp.Contains(pt)) {
|
||||
bts.Log.assert(_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)));
|
||||
_toggle_mouse_over_text();
|
||||
return;
|
||||
}
|
||||
if (_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)))
|
||||
_toggle_mouse_over_text();
|
||||
}
|
||||
|
||||
void _on_mouse_changed(Point pt) {
|
||||
_is_mouse_over_text = _gcr.content_area.Expanded(1).Contains(pt);
|
||||
CoerceValue(IsMouseOverClickAreaProperty);
|
||||
void _toggle_mouse_over_text() {
|
||||
_is_mouse_over_text = !_is_mouse_over_text;
|
||||
if (_is_mouse_over_text)
|
||||
SetCurrentValue(CursorProperty, Cursors.Hand);
|
||||
else
|
||||
ClearValue(CursorProperty);
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -15,7 +15,6 @@ namespace mbts.ui.controls {
|
||||
Brush _background, _foreground, _border_brush_backing;
|
||||
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
|
||||
GlyphRun _glyph_run => _gcr.glyph_run;
|
||||
bool _snaps_to_device_pixels;
|
||||
Pen _border_stroke_pen_backing;
|
||||
GlyphContentRect _gcr = new GlyphContentRect();
|
||||
|
||||
@@ -27,7 +26,7 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
Thickness _border_thickness {
|
||||
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
|
||||
get => _gcr.border_thickness;
|
||||
set {
|
||||
_gcr.border_thickness = new Thickness(value.Left);
|
||||
_border_stroke_pen_backing = null;
|
||||
@@ -53,11 +52,6 @@ namespace mbts.ui.controls {
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.width = v,
|
||||
double.NaN);
|
||||
WPFHelper.override_metadata<bool, TextCell>(
|
||||
SnapsToDevicePixelsProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._snaps_to_device_pixels = v,
|
||||
true);
|
||||
}
|
||||
|
||||
public TextCell() {
|
||||
@@ -80,7 +74,6 @@ namespace mbts.ui.controls {
|
||||
if (Text is string str && !string.IsNullOrEmpty(str))
|
||||
_gcr.text_lines = new[] { str };
|
||||
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
|
||||
_snaps_to_device_pixels = SnapsToDevicePixels;
|
||||
}
|
||||
|
||||
#region BorderThickness
|
||||
@@ -118,7 +111,14 @@ namespace mbts.ui.controls {
|
||||
new FrameworkPropertyMetadata(
|
||||
HorizontalAlignment.Center,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange,
|
||||
(d, e) => ((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
|
||||
(d, e) => {
|
||||
if (e.OldValue is HorizontalAlignment oldha) {
|
||||
if (oldha == HorizontalAlignment.Right) {
|
||||
|
||||
}
|
||||
}
|
||||
((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue;
|
||||
}));
|
||||
|
||||
public HorizontalAlignment HorizontalContentAlignment {
|
||||
get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
|
||||
@@ -130,8 +130,7 @@ namespace mbts.ui.controls {
|
||||
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(TextCell),
|
||||
new FrameworkPropertyMetadata(
|
||||
VerticalAlignment.Center,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange,
|
||||
(d, e) => ((TextCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
|
||||
|
||||
public VerticalAlignment VerticalContentAlignment {
|
||||
@@ -211,7 +210,8 @@ namespace mbts.ui.controls {
|
||||
nameof(Padding),
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(p, v) => p._gcr.padding = default, default);
|
||||
(p, v) => p._gcr.padding = v,
|
||||
DIYGrid.DefaultCellPadding);
|
||||
|
||||
public Thickness Padding { get => (Thickness)GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); }
|
||||
#endregion
|
||||
@@ -237,17 +237,35 @@ namespace mbts.ui.controls {
|
||||
return _gcr.layout_size;
|
||||
}
|
||||
|
||||
double bl_x = 0;
|
||||
protected override void OnRender(DrawingContext drawingContext) {
|
||||
var rs = RenderSize;
|
||||
var rect = new Rect(0, 0, rs.Width, rs.Height);
|
||||
var bg = Background;
|
||||
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
|
||||
var gr = _glyph_run;
|
||||
if (gr != null) {
|
||||
if (bg != null) {
|
||||
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
|
||||
}
|
||||
if (_glyph_run != null) {
|
||||
var new_x = _glyph_run.BaselineOrigin.X;
|
||||
if (Math.Abs(bl_x - new_x) > 5) {
|
||||
|
||||
if (new_x < bl_x) {
|
||||
|
||||
}
|
||||
}
|
||||
if (Text == "-3,375.000") {
|
||||
|
||||
}
|
||||
bl_x = new_x;
|
||||
drawingContext.PushClip(new RectangleGeometry(rect));
|
||||
drawingContext.DrawGlyphRun(_foreground, gr);
|
||||
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
|
||||
drawingContext.Pop();
|
||||
}
|
||||
if (bg != null) {
|
||||
var bgo = DIYGrid.GetBackgroundOverlay(this);
|
||||
if (bgo != null)
|
||||
drawingContext.DrawRectangle(bgo, null, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,6 @@ namespace mbts.ui.controls {
|
||||
Aggressor = "Aggressor",
|
||||
Volume = "Volume",
|
||||
Last = "Last",
|
||||
S = "S",
|
||||
Pos = "Pos",
|
||||
Working = "Working",
|
||||
Bid = "Bid",
|
||||
@@ -125,7 +124,6 @@ namespace mbts.ui.controls {
|
||||
SessionLow = "Low",
|
||||
SessionHigh = "High",
|
||||
FillKind = "Fill Kind",
|
||||
FillStatus = "Fill Status",
|
||||
Name = "Name",
|
||||
Description = "Description",
|
||||
N_Legs = "# Legs",
|
||||
|
||||
@@ -1,76 +1,30 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
// Column types are nested inside the DIYGrid
|
||||
partial class DIYGrid {
|
||||
public readonly struct CellGeometry {
|
||||
public CellGeometry(ColumnGeometry column, ListBoxItem row) {
|
||||
this.column = column;
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
public ColumnGeometry column { get; }
|
||||
public ListBoxItem row { get; }
|
||||
|
||||
public void Deconstruct(out ListBoxItem row, out ColumnGeometry column) {
|
||||
row = this.row;
|
||||
column = this.column;
|
||||
}
|
||||
|
||||
public Rect to_rect(double y) =>
|
||||
column.to_visible_rect(row.ActualHeight, y);
|
||||
|
||||
public static implicit operator (ListBoxItem, ColumnGeometry) (CellGeometry cell) =>
|
||||
(cell.row, cell.column);
|
||||
}
|
||||
|
||||
public readonly struct ColumnGeometry : IComparable<ColumnGeometry> {
|
||||
public static readonly IEqualityComparer<ColumnGeometry> PositionEqualityComparer =
|
||||
EqualityComparer.Create<ColumnGeometry, (double, double)>(c => (c.left, c.right));
|
||||
|
||||
public readonly ColumnBase column;
|
||||
public readonly DIYGridSuperHeader super_column;
|
||||
public readonly double
|
||||
left,
|
||||
// If the column was unclipped
|
||||
unclipped_left,
|
||||
right;
|
||||
|
||||
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
|
||||
: this(column, super_column, left, left, right) { }
|
||||
|
||||
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
|
||||
this.column = column;
|
||||
this.super_column = super_column;
|
||||
this.left = left;
|
||||
this.unclipped_left = unclipped_left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public double width => right - left;
|
||||
public struct ColumnGeometry {
|
||||
public ColumnBase column;
|
||||
public DIYGridSuperHeader super_column;
|
||||
public double unclipped_left; /* If the column was unclipped */
|
||||
public double left;
|
||||
public double width { get { return column.ActualWidth; } }
|
||||
public double right { get { return unclipped_left + width; } }
|
||||
public bool is_partially_obscured { get { return unclipped_left < left; } }
|
||||
public double visible_width => Math.Max(0, width - left + unclipped_left);
|
||||
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
|
||||
|
||||
public Rect to_rect(double row_height) =>
|
||||
new Rect(left, 0, width, row_height);
|
||||
|
||||
public Rect to_visible_rect(double row_height, double y) =>
|
||||
new Rect(unclipped_left, y, visible_width, row_height);
|
||||
|
||||
public override string ToString() {
|
||||
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
|
||||
}
|
||||
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
|
||||
|
||||
public int CompareTo(ColumnGeometry other) {
|
||||
int comp = left.CompareTo(other.left);
|
||||
if (comp != 0)
|
||||
return comp;
|
||||
return right.CompareTo(other.right);
|
||||
public RectangleGeometry get_clip(double row_height) =>
|
||||
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
|
||||
|
||||
public IDisposable clip_draw(DrawingContext dc, double row_height) {
|
||||
dc.PushClip(get_clip(row_height));
|
||||
return Disposable.Create(dc.Pop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-50
@@ -1,72 +1,40 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.converters;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
internal class HyperlinkElementColumn<TKey> : ColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
|
||||
internal class HyperlinkElementColumn<TRowVM, TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
|
||||
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
|
||||
readonly BindingBase _binding, _command_binding;
|
||||
|
||||
public HyperlinkElementColumn(TKey key, IDataGridColumnFilter filter, string filter_template)
|
||||
: base(key) {
|
||||
public HyperlinkElementColumn(
|
||||
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
_binding = binding;
|
||||
_command_binding = command_binding;
|
||||
}
|
||||
|
||||
public event Action<HyperlinkCell> view_generated;
|
||||
|
||||
public Style ElementStyle { get; set; }
|
||||
public IDataGridColumnFilter filter { get; set; }
|
||||
public string filter_template { get; set; }
|
||||
public HyperlinkCellClickMode HyperlinkClickMode { get; set; }
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public string filter_template { get; }
|
||||
public Thickness padding { get; set; } = _default_padding;
|
||||
// bindings
|
||||
public BindingBase CommandBinding { get; set; }
|
||||
public BindingBase CommandParameterBinding { get; set; }
|
||||
public BindingBase TextBinding { get; set; }
|
||||
|
||||
public FrameworkElement view_factory() {
|
||||
var hl = new HyperlinkCell {
|
||||
HyperlinkClickMode = HyperlinkClickMode,
|
||||
Padding = padding
|
||||
};
|
||||
hl.SetCurrentValue(DIYGrid.ColumnProperty, this);
|
||||
// bindings
|
||||
if (TextBinding != null)
|
||||
hl.SetBinding(HyperlinkCell.TextProperty, TextBinding);
|
||||
if (CommandBinding != null)
|
||||
hl.SetBinding(HyperlinkCell.CommandProperty, CommandBinding);
|
||||
if (CommandParameterBinding != null)
|
||||
hl.SetBinding(HyperlinkCell.CommandParameterProperty, CommandParameterBinding);
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var hl = new HyperlinkCell { Padding = padding };
|
||||
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
|
||||
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
hl.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
hl.SetBinding(
|
||||
CursorProperty,
|
||||
new Binding {
|
||||
Source = hl,
|
||||
Path = new PropertyPath(HyperlinkCell.IsMouseOverClickAreaProperty),
|
||||
Converter = new BooleanToObjectConverter {
|
||||
ValueForFalse = Cursors.Arrow,
|
||||
ValueForTrue = Cursors.Hand
|
||||
}
|
||||
});
|
||||
// background
|
||||
var bg_brush = new SolidColorBrush();
|
||||
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
|
||||
hl.SetCurrentValue(HyperlinkCell.BackgroundProperty, bg_brush);
|
||||
// style
|
||||
if (ElementStyle != null)
|
||||
hl.Style = ElementStyle;
|
||||
view_generated?.Invoke(hl);
|
||||
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
|
||||
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
|
||||
return hl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-29
@@ -1,50 +1,44 @@
|
||||
using mbts.spark.filters;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
internal class TextColumn<TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
|
||||
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
|
||||
internal class TextColumn<TColKey> : FrameworkElementColumnBase<TColKey>, IFilterableColumn {
|
||||
readonly BindingBase _binding;
|
||||
|
||||
public TextColumn(
|
||||
TKey key, IDataGridColumnFilter filter, string filter_template) : base(key) {
|
||||
public TextColumn(TColKey key, BindingBase binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
_binding = binding;
|
||||
}
|
||||
|
||||
public Style ElementStyle { get; set; }
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public string filter_template { get; }
|
||||
public Thickness padding { get; set; } = _default_padding;
|
||||
// bindings
|
||||
public BindingBase TextBinding { get; set; }
|
||||
public BindingBase ToolTipBinding { get; set; }
|
||||
public BindingBase background_binding { get; set; }
|
||||
public BindingBase font_weight_binding { get; set; }
|
||||
public BindingBase foreground_binding { get; set; }
|
||||
public BindingBase tooltip_binding { get; set; }
|
||||
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var v = new TextCell {
|
||||
Padding = padding
|
||||
};
|
||||
// bindings
|
||||
if (TextBinding != null)
|
||||
v.SetBinding(TextCell.TextProperty, TextBinding);
|
||||
if (ToolTipBinding != null)
|
||||
v.SetBinding(ToolTipProperty, ToolTipBinding);
|
||||
v.SetBinding(TextCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
var v = new TextCell { Foreground = font_brush, VerticalAlignment = vertical_alignment };
|
||||
v.SetBinding(TextCell.TextProperty, _binding);
|
||||
v.SetBinding(TextCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
v.SetBinding(TextCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
v.SetBinding(TextCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
v.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
// background
|
||||
var bg_brush = new SolidColorBrush();
|
||||
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
|
||||
v.Background = bg_brush;
|
||||
// style
|
||||
if (ElementStyle != null)
|
||||
v.Style = ElementStyle;
|
||||
v.SetBinding(TextCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
v.SetBinding(TextCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
try_set_binding(TextCell.BackgroundProperty, background_binding);
|
||||
try_set_binding(TextCell.ForegroundProperty, foreground_binding);
|
||||
try_set_binding(TextCell.FontWeightProperty, font_weight_binding);
|
||||
try_set_binding(TextCell.ToolTipProperty, tooltip_binding);
|
||||
return v;
|
||||
//
|
||||
void try_set_binding(DependencyProperty p, BindingBase b) {
|
||||
if (b != null)
|
||||
v.SetBinding(p, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -25,7 +22,6 @@ using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
interface IHandleGridSorting {
|
||||
@@ -78,26 +74,6 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset_sorting<TKey>(IEnumerable<DIYGrid.ColumnBase> columns, IEnumerable<SortingCriteria> sorting_criterion) where TKey : struct =>
|
||||
reset_sorting(columns, convert_sorting_criterion<TKey>(sorting_criterion));
|
||||
|
||||
public static void reset_sorting<TKey>(IEnumerable<DIYGrid.ColumnBase> columns, IEnumerable<(TKey key, ListSortDirection direction)> sorting_hierarchy) where TKey : struct {
|
||||
// TODO: incorporate the sort hierarchy/index, for nested sorting
|
||||
var active_cols = sorting_hierarchy.empty_if_null()
|
||||
.Select((k, i) => (k, i))
|
||||
.ToDictionary(tup => tup.k.key, tup => (tup.k.direction, tup.i));
|
||||
// columns are dispatcher objects, and might throw if they are accessed after the view has been shut down
|
||||
try {
|
||||
foreach (var c in columns) {
|
||||
if (Enum.TryParse(c.id, true, out TKey key) && active_cols.TryGetValue(key, out var tup)) {
|
||||
c.SortDirection = tup.direction;
|
||||
} else {
|
||||
c.SortDirection = null;
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
public static IObservable<ImmutableList<(TKey key, IComparer<TRow> comparer, ListSortDirection direction)>> observe_active_comparers<TKey, TRow>(
|
||||
DIYGrid grid, Func<TKey, IComparer<TRow>> get_comparer, bool mark_handled = true) =>
|
||||
//
|
||||
@@ -230,11 +206,10 @@ namespace mbts.ui.controls {
|
||||
double _row_height = DIYGrid.DefaultRowHeight;
|
||||
ScrollContentPresenter _content_presenter_backing;
|
||||
DIYGrid _grid;
|
||||
AdornerHost<Rectangle> _drag_rect_adorner;
|
||||
DragRectAdorner _drag_rect_adorner;
|
||||
RowSelectionAdorner _rsa;
|
||||
DIYGridCellHoverAdorner _cell_hover_adorner;
|
||||
ScrollViewer _scroll_viewer;
|
||||
Point? _drag_start, _drag_stop;
|
||||
Point? _drag_start;
|
||||
double _drag_start_vertical_drift;
|
||||
bool _support_group_items;
|
||||
int _drag_start_index;
|
||||
@@ -244,10 +219,7 @@ namespace mbts.ui.controls {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGridRowsPresenter), new FrameworkPropertyMetadata(typeof(DIYGridRowsPresenter)));
|
||||
|
||||
public DIYGridRowsPresenter() {
|
||||
Loaded += on_loaded;
|
||||
Unloaded += on_unloaded;
|
||||
//
|
||||
void on_loaded(object sender, RoutedEventArgs e) {
|
||||
Loaded += (s, e) => {
|
||||
if (_grid != null)
|
||||
_grid.RowHeightChanged -= on_row_height_changed;
|
||||
_grid = this.find_ancestor_of_type<DIYGrid>();
|
||||
@@ -256,111 +228,31 @@ namespace mbts.ui.controls {
|
||||
_support_group_items = _grid.SupportGroupItems;
|
||||
_grid.SupportGroupItemsChanged += on_support_group_items_changed;
|
||||
_scroll_viewer = this.descendants_of_type<ScrollViewer>().First();
|
||||
var explicit_drag_src = Observable.Create<bool>(o => {
|
||||
_begin_explicit_drag += o.OnNext;
|
||||
return Disposable.Create(() => _begin_explicit_drag -= o.OnNext);
|
||||
});
|
||||
DragDropHelper.subscribe_drag_coordinates(this, init_element: _content_presenter, use_preview_events: true, explicit_drag: explicit_drag_src).Subscribe(pt => {
|
||||
if (_drag_start.HasValue) {
|
||||
if (pt.HasValue) {
|
||||
_drag_stop = pt.Value;
|
||||
_refresh_drag_rect();
|
||||
} else {
|
||||
if (_drag_stop.HasValue) {
|
||||
_end_drag(_drag_stop.Value);
|
||||
}
|
||||
_drag_start = null;
|
||||
_drag_stop = null;
|
||||
}
|
||||
} else if (pt.HasValue) {
|
||||
_begin_drag_selection(pt.Value);
|
||||
}
|
||||
});
|
||||
// cell hover adorner
|
||||
_cell_hover_adorner = new DIYGridCellHoverAdorner(this, _scroll_viewer);
|
||||
_cell_hover_adorner.SetBinding(VisibilityProperty, new Binding {
|
||||
Path = new PropertyPath(IsMouseOverProperty),
|
||||
Converter = new converters.BooleanToVisibilityConverter(),
|
||||
Source = this
|
||||
});
|
||||
// Uncomment call below to activate cell hover feedback.
|
||||
// In the meantime, cells handle the mouse hover feedback individually.
|
||||
//set_selection_display_bindings(
|
||||
// _cell_hover_adorner,
|
||||
// fill_prop: DIYGridCellHoverAdorner.CellFillProperty,
|
||||
// fill_converter: DIYGrid.CellHoverHighlightBrushConverter,
|
||||
// DIYGridCellHoverAdorner.CellStrokeProperty,
|
||||
// DIYGrid.CellHoverHighlightPenConverter,
|
||||
// bind_opacity: false);
|
||||
_set_selection_display_bindings(
|
||||
_cell_hover_adorner,
|
||||
fill_prop: DIYGridCellHoverAdorner.RowFillProperty,
|
||||
fill_converter: DIYGrid.RowHoverHighlightBrushConverter,
|
||||
//stroke_prop: DIYGridCellHoverAdorner.RowStrokeProperty,
|
||||
//stroke_converter: DIYGrid.CellHoverHighlightPenConverter,
|
||||
bind_opacity: false);
|
||||
_cell_hover_adorner.attach();
|
||||
// row selection adorner
|
||||
if (SelectionMode != SelectionMode.Single) {
|
||||
_drag_rect_adorner = new DragRectAdorner(this, _scroll_viewer, _row_height);
|
||||
_drag_rect_adorner.SetResourceReference(DragRectAdorner.FillProperty, DIYGrid.ActiveSelectionHighlightKey);
|
||||
_drag_rect_adorner.SetResourceReference(DragRectAdorner.StrokeProperty, DIYGrid.SelectionStrokeKey);
|
||||
_drag_rect_adorner.attach();
|
||||
}
|
||||
_rsa = new RowSelectionAdorner(this, _scroll_viewer);
|
||||
_set_selection_display_bindings(
|
||||
_rsa,
|
||||
fill_prop: RowSelectionAdorner.FillProperty,
|
||||
fill_converter: DIYGrid.SelectionHighlightBrushConverter,
|
||||
stroke_prop: RowSelectionAdorner.StrokeProperty,
|
||||
stroke_converter: DIYGrid.SelectionHighlightPenConverter);
|
||||
_rsa.attach();
|
||||
}
|
||||
|
||||
void on_unloaded(object sender, RoutedEventArgs e) {
|
||||
};
|
||||
Unloaded += (s, e) => {
|
||||
if (_drag_rect_adorner != null) {
|
||||
_drag_rect_adorner.detach();
|
||||
_drag_rect_adorner = null;
|
||||
Disposable.dispose(ref _drag_rect_adorner);
|
||||
}
|
||||
if (_rsa != null) {
|
||||
_rsa.detach();
|
||||
Disposable.dispose(ref _rsa);
|
||||
}
|
||||
if (_cell_hover_adorner != null) {
|
||||
_cell_hover_adorner.detach();
|
||||
Disposable.dispose(ref _cell_hover_adorner);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
void on_row_height_changed(double v) => _row_height = v;
|
||||
void on_support_group_items_changed(bool v) => _support_group_items = v;
|
||||
}
|
||||
|
||||
void _refresh_drag_rect() {
|
||||
var pres = _content_presenter;
|
||||
if (pres == null)
|
||||
return;
|
||||
//var left = Math.Min(_drag_start.Value.X, _drag_stop.Value.X);
|
||||
//var top = Math.Min(_drag_start.Value.Y, _drag_stop.Value.Y);
|
||||
//var width = Math.Abs(_drag_start.Value.X - _drag_stop.Value.X);
|
||||
//var height = Math.Abs(_drag_start.Value.Y - _drag_stop.Value.Y);
|
||||
|
||||
//var clipped_rect = this.GetChildBounds(pres);
|
||||
//clipped_rect.Intersect(new Rect(left, top, width, height));
|
||||
//_drag_rect_adorner.Child.Margin = new Thickness(clipped_rect.Left, clipped_rect.Top, 0, 0);
|
||||
//_drag_rect_adorner.Child.Width = clipped_rect.Width;
|
||||
//_drag_rect_adorner.Child.Height = clipped_rect.Height;
|
||||
|
||||
var left = Math.Min(_drag_start.Value.X, _drag_stop.Value.X);
|
||||
var top = Math.Min(_drag_start.Value.Y, _drag_stop.Value.Y);
|
||||
//var tl = TranslatePoint(new Point(left, top), pres);
|
||||
//var br = TranslatePoint(new Point(
|
||||
// tl.X + Math.Abs(_drag_start.Value.X - _drag_stop.Value.X),
|
||||
// tl.Y + Math.Abs(_drag_start.Value.Y - _drag_stop.Value.Y)), pres);
|
||||
double vdiff = _drag_start_vertical_drift - _scroll_viewer.VerticalOffset;
|
||||
_drag_rect_adorner.Child.Margin = new Thickness(left, top, 0, 0);
|
||||
_drag_rect_adorner.Child.Width = Math.Abs(_drag_start.Value.X - _drag_stop.Value.X);
|
||||
_drag_rect_adorner.Child.Height = Math.Abs(_drag_start.Value.Y - _drag_stop.Value.Y) + Math.Abs(vdiff);
|
||||
}
|
||||
|
||||
public event GroupItemSelectionPivotEventHandler GroupItemSelectionPivot;
|
||||
|
||||
event Action<bool> _begin_explicit_drag;
|
||||
|
||||
ScrollContentPresenter _content_presenter => _content_presenter_backing ??
|
||||
(_content_presenter_backing = this.descendants_of_type<ScrollContentPresenter>().FirstOrDefault());
|
||||
|
||||
@@ -372,28 +264,8 @@ namespace mbts.ui.controls {
|
||||
base.UnselectAll();
|
||||
}
|
||||
|
||||
public void begin_explicit_drag() {
|
||||
if (_begin_explicit_drag != null) {
|
||||
Mouse.Capture(this, CaptureMode.SubTree);
|
||||
if (!_drag_start.HasValue) {
|
||||
if (_begin_explicit_drag == null) {
|
||||
// capturing the mouse caused listener(s) to unhook from the event
|
||||
// release capture since the drag selection has been aborted.
|
||||
ReleaseMouseCapture();
|
||||
} else {
|
||||
var pt = Mouse.GetPosition(this);
|
||||
if (this.ChildAtPoint(pt, out ListBoxItem row)) {
|
||||
row.Focus();
|
||||
Keyboard.Focus(row);
|
||||
}
|
||||
_begin_explicit_drag(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _begin_drag_selection(Point pt) {
|
||||
_drag_rect_adorner?.detach();
|
||||
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
|
||||
var pt = e.GetPosition(this);
|
||||
if (InputHitTest(pt) is UIElement el) {
|
||||
var sb = new StringBuilderEx("beginning drag selection on ");
|
||||
OneOf.MatchFirst<DependencyObject, ListBoxItem, GroupItem>(el.VisualAncestors().Prepend(el)).Match(init_common, on_group);
|
||||
@@ -407,45 +279,22 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
void init_common(ListBoxItem item_container) {
|
||||
// drag state
|
||||
_drag_start = pt;
|
||||
_drag_stop = pt;
|
||||
_drag_start_item = ItemContainerGenerator.ItemFromContainer(item_container);
|
||||
_drag_start_index = ItemContainerGenerator.IndexFromContainer(item_container);
|
||||
_drag_start_vertical_drift = _scroll_viewer.VerticalOffset;
|
||||
if (!IsKeyboardFocusWithin)
|
||||
Keyboard.Focus(item_container);
|
||||
// drag rect
|
||||
_drag_rect_adorner = new AdornerHost<Rectangle>(_content_presenter ?? (UIElement)this) {
|
||||
Child = new Rectangle {
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
StrokeThickness = HighDpiExtensions.pixel_unit
|
||||
}
|
||||
};
|
||||
_set_selection_display_bindings(
|
||||
_drag_rect_adorner.Child,
|
||||
fill_prop: Shape.FillProperty,
|
||||
fill_converter: DIYGrid.SelectionHighlightBrushConverter,
|
||||
stroke_prop: Shape.StrokeProperty,
|
||||
stroke_converter: ValueConverter.create<Color, Brush>(c => new SolidColorBrush(c.with(255))));
|
||||
_refresh_drag_rect();
|
||||
_drag_rect_adorner.attach();
|
||||
// log
|
||||
sb.Add($"'{_drag_start_item}' (selection mode {SelectionMode}).");
|
||||
_log.info(sb.ToString());
|
||||
}
|
||||
}
|
||||
base.OnPreviewMouseLeftButtonDown(e);
|
||||
}
|
||||
|
||||
void _end_drag(Point drag_stop) {
|
||||
if (_drag_rect_adorner != null) {
|
||||
_drag_rect_adorner.detach();
|
||||
_drag_rect_adorner = null;
|
||||
}
|
||||
protected override void OnPreviewMouseUp(MouseButtonEventArgs e) {
|
||||
if (_drag_start.HasValue) {
|
||||
_log.info("ending drag selection on mouse up.");
|
||||
if (SelectionMode != SelectionMode.Single && _content_presenter != null) {
|
||||
var drag_stop = e.GetPosition(this);
|
||||
drag_stop.X = MathUtils.within_range(0, _content_presenter.RenderSize.Width - 1, drag_stop.X);
|
||||
drag_stop.Y = MathUtils.within_range(0, _content_presenter.RenderSize.Height - 1, drag_stop.Y);
|
||||
double vertical_diff = drag_stop.Y + (_row_height * (_scroll_viewer.VerticalOffset - _drag_start_vertical_drift)) - _drag_start.Value.Y;
|
||||
@@ -507,6 +356,7 @@ namespace mbts.ui.controls {
|
||||
_drag_start = null;
|
||||
_drag_start_item = null;
|
||||
}
|
||||
base.OnPreviewMouseUp(e);
|
||||
}
|
||||
|
||||
void _select_items_between(object inclusive_begin, object inclusive_end) {
|
||||
@@ -525,36 +375,6 @@ namespace mbts.ui.controls {
|
||||
.BookEnd(inclusive_begin, inclusive_end));
|
||||
}
|
||||
}
|
||||
|
||||
void _set_selection_display_bindings(DependencyObject target, DependencyProperty fill_prop = null, IValueConverter fill_converter = null, DependencyProperty stroke_prop = null, IValueConverter stroke_converter = null, bool bind_opacity = true) {
|
||||
// fill
|
||||
if (fill_prop != null) {
|
||||
BindingOperations.SetBinding(target, fill_prop,
|
||||
new Binding {
|
||||
Source = _grid,
|
||||
Path = new PropertyPath(DIYGrid.SelectionHighlightProperty),
|
||||
Converter = fill_converter
|
||||
});
|
||||
}
|
||||
// stroke
|
||||
if (stroke_prop != null) {
|
||||
BindingOperations.SetBinding(target, stroke_prop,
|
||||
new Binding {
|
||||
Source = _grid,
|
||||
Path = new PropertyPath(DIYGrid.SelectionHighlightProperty),
|
||||
Converter = stroke_converter
|
||||
});
|
||||
}
|
||||
// opacity
|
||||
if (bind_opacity) {
|
||||
BindingOperations.SetBinding(target, OpacityProperty,
|
||||
new Binding {
|
||||
Source = this,
|
||||
Path = new PropertyPath(IsSelectionActiveProperty),
|
||||
Converter = ValueConverter.create<bool, double>(is_active => is_active ? 1.0 : 0.5)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class DIYGrid : Control {
|
||||
@@ -591,48 +411,55 @@ namespace mbts.ui.controls {
|
||||
// TODO: SelectionFill/Stroke should not be hardcoded, for now this serves as the central location for DragRectAdorner,
|
||||
// and FrameworkElementColumns that paint the selection brush themselves
|
||||
// (e.g MarketView bid/ask columns, bc their background is not transparent, it covers the ListBoxItem selection-highlight).
|
||||
public static readonly Color DefaultSelectionHighlight = Colors.MediumSlateBlue.with(0x63);
|
||||
|
||||
public static readonly SolidColorBrush DefaultSelectionHighlight = new SolidColorBrush(Colors.MediumSlateBlue) { Opacity = 0.4 };
|
||||
public static readonly Pen DefaultSelectionStroke = new Pen(Brushes.MediumSlateBlue, 1);
|
||||
readonly NotifyCollectionChangedEventHandler _sort_descriptions_collection_changed_cb;
|
||||
double _inactive_selection_opacity = 0.2;
|
||||
DataContextHooks _data_context_hooks;
|
||||
|
||||
public static readonly DependencyProperty SelectionHighlightProperty =
|
||||
WPFHelper.create_dp<Color, DIYGrid>(nameof(SelectionHighlight), DefaultSelectionHighlight);
|
||||
WPFHelper.create_dp<SolidColorBrush, DIYGrid>(
|
||||
nameof(SelectionHighlight),
|
||||
(o, v) => {
|
||||
o.Resources[ActiveSelectionHighlightKey] = v;
|
||||
o._reset_inactive_selection_brush();
|
||||
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_solid_brush().frozen(), 1).frozen();
|
||||
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
|
||||
col.on_selection_fill(v);
|
||||
var ad_layer = System.Windows.Documents.AdornerLayer.GetAdornerLayer(o);
|
||||
if (ad_layer != null) {
|
||||
foreach (var ad in ad_layer.descendants_of_type<ISelectionHighlightListener>()) {
|
||||
ad.on_selection_fill(v);
|
||||
}
|
||||
}
|
||||
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
|
||||
DefaultSelectionHighlight);
|
||||
|
||||
public Color SelectionHighlight {
|
||||
get => (Color)GetValue(SelectionHighlightProperty);
|
||||
public SolidColorBrush SelectionHighlight {
|
||||
get => GetValue(SelectionHighlightProperty) as SolidColorBrush;
|
||||
set => SetValue(SelectionHighlightProperty, value);
|
||||
}
|
||||
|
||||
static DIYGrid() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(typeof(DIYGrid)));
|
||||
UseLayoutRoundingProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(OnUseLayoutRoundingChanged));
|
||||
|
||||
void OnUseLayoutRoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
|
||||
d.CoerceValue(RowHeightProperty);
|
||||
DefaultSelectionHighlight.Freeze();
|
||||
DefaultSelectionStroke.Freeze();
|
||||
}
|
||||
|
||||
public DIYGrid() {
|
||||
var sort_map = new Dictionary<string, ColumnBase>();
|
||||
_sort_descriptions_collection_changed_cb = WPFHelper.create_collection_changed_cb<SortDescription>(
|
||||
sd => {
|
||||
if (!SyncColumnSortDirectionWithSortDescriptions)
|
||||
return;
|
||||
if (Columns.TryGetFirst(c => c.sort_member_path == sd.PropertyName, out ColumnBase col)) {
|
||||
sort_map[sd.PropertyName] = col;
|
||||
col.SortDirection = sd.Direction;
|
||||
}
|
||||
}, sd => {
|
||||
if (!SyncColumnSortDirectionWithSortDescriptions)
|
||||
return;
|
||||
if (Columns.TryGetFirst(c => c.sort_member_path == sd.PropertyName, out ColumnBase col)) {
|
||||
col.SortDirection = null;
|
||||
sort_map.Remove(sd.PropertyName);
|
||||
}
|
||||
}, s => {
|
||||
if (!SyncColumnSortDirectionWithSortDescriptions)
|
||||
return;
|
||||
sort_map.Clear();
|
||||
if (Columns is IEnumerable<ColumnBase> cols) {
|
||||
foreach (var c in cols)
|
||||
@@ -678,6 +505,7 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
}));
|
||||
Resources[ActiveSelectionHighlightKey] = SelectionHighlight;
|
||||
_reset_inactive_selection_brush();
|
||||
_data_context_hooks = new DataContextHooks(this, DataContext);
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
@@ -686,21 +514,6 @@ namespace mbts.ui.controls {
|
||||
public event DIYGridSortingEventHandler Sorting;
|
||||
public event Action<bool> SupportGroupItemsChanged;
|
||||
|
||||
public static IValueConverter SelectionHighlightPenConverter =>
|
||||
ValueConverter.create<Color, Pen>(c => FrozenPenCache.get_pen(c.with(255)));
|
||||
|
||||
public static IValueConverter SelectionHighlightBrushConverter =>
|
||||
ValueConverter.create<Color, Brush>(FrozenBrushCache.get_brush);
|
||||
|
||||
public static IValueConverter RowHoverHighlightBrushConverter =>
|
||||
ValueConverter.create<Color, Brush>(c => FrozenBrushCache.get_brush(c, 0.35));
|
||||
|
||||
public static IValueConverter CellHoverHighlightPenConverter =>
|
||||
ValueConverter.create<Color, Pen>(c => FrozenPenCache.get_pen(c.with(255)));
|
||||
|
||||
public static IValueConverter CellHoverHighlightBrushConverter =>
|
||||
ValueConverter.create<Color, Brush>(c => FrozenBrushCache.get_brush(c, 0.5));
|
||||
|
||||
public double InactiveSelectionOpacity {
|
||||
get => _inactive_selection_opacity;
|
||||
set {
|
||||
@@ -721,8 +534,6 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public bool SyncColumnSortDirectionWithSortDescriptions { get; set; } = true;
|
||||
|
||||
public ItemContainerGenerator RowContainerGenerator =>
|
||||
_row_presenter.ItemContainerGenerator;
|
||||
|
||||
@@ -1022,14 +833,6 @@ namespace mbts.ui.controls {
|
||||
// Public Properties
|
||||
#region Public Properties
|
||||
|
||||
#region Column
|
||||
public static readonly DependencyProperty ColumnProperty =
|
||||
DependencyProperty.RegisterAttached("Column", typeof(ColumnBase), typeof(DIYGrid));
|
||||
|
||||
public static ColumnBase GetColumn(DependencyObject d) => d.GetValue(ColumnProperty) as ColumnBase;
|
||||
public static void SetColumn(DependencyObject d, ColumnBase value) => d.SetValue(ColumnProperty, value);
|
||||
#endregion
|
||||
|
||||
#region ColumnKey
|
||||
public static readonly DependencyProperty ColumnKeyProperty =
|
||||
DependencyProperty.RegisterAttached("ColumnKey",
|
||||
@@ -1266,7 +1069,7 @@ namespace mbts.ui.controls {
|
||||
if (v < min_row_height)
|
||||
return min_row_height;
|
||||
}
|
||||
return o.UseLayoutRounding ? v.RoundLayoutValue() : v;
|
||||
return v;
|
||||
}, DefaultRowHeight);
|
||||
|
||||
public event Action<double> RowHeightChanged;
|
||||
@@ -1287,11 +1090,14 @@ namespace mbts.ui.controls {
|
||||
public static readonly DependencyProperty RowsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, DIYGrid>(
|
||||
nameof(Rows),
|
||||
(s, oldval, newval) =>
|
||||
WPFHelper.manage_collection_change(
|
||||
(oldval as ICollectionView)?.SortDescriptions,
|
||||
(newval as ICollectionView)?.SortDescriptions,
|
||||
s._sort_descriptions_collection_changed_cb),
|
||||
(s, oldval, newval) => {
|
||||
WPFHelper.manage_collection_change(
|
||||
(oldval as ICollectionView)?.SortDescriptions,
|
||||
(newval as ICollectionView)?.SortDescriptions,
|
||||
s._sort_descriptions_collection_changed_cb,
|
||||
old_ncc => s._sort_descriptions_collection_changed_cb(old_ncc, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)),
|
||||
new_ncc => s._sort_descriptions_collection_changed_cb(new_ncc, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)new_ncc)));
|
||||
},
|
||||
Enumerable.Empty<object>());
|
||||
|
||||
public DataTemplate RowTemplate {
|
||||
@@ -1340,30 +1146,33 @@ namespace mbts.ui.controls {
|
||||
double frozen_offset = 0;
|
||||
int vis_frozen_col_count = _get_visible_frozen_column_count();
|
||||
foreach (var col in _visible_columns.Take(vis_frozen_col_count)) {
|
||||
double right = frozen_offset + col.ActualWidth;
|
||||
right = right.RoundLayoutValue();
|
||||
yield return new ColumnGeometry(col, null, frozen_offset, frozen_offset, right);
|
||||
yield return new ColumnGeometry() {
|
||||
column = col,
|
||||
left = frozen_offset,
|
||||
unclipped_left = frozen_offset,
|
||||
};
|
||||
frozen_offset += col.ActualWidth;
|
||||
}
|
||||
if (_frozen_column_count > 0) {
|
||||
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
|
||||
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
|
||||
frozen_offset += 2;
|
||||
}
|
||||
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
|
||||
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
|
||||
frozen_offset += 2;
|
||||
// Adjust by scroll amount
|
||||
double offset = frozen_offset - _scroll_horizontal.Value;
|
||||
offset = offset.RoundLayoutValue();
|
||||
double offset = frozen_offset - (_scroll_horizontal.Value);
|
||||
int count = 0;
|
||||
foreach (var col in _visible_columns.Skip(vis_frozen_col_count)) {
|
||||
double left = Math.Max(offset, frozen_offset);
|
||||
double right = offset + col.ActualWidth;
|
||||
right = right.RoundLayoutValue();
|
||||
// If right < frozen_offset column is completely obscured
|
||||
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
|
||||
// fully visible
|
||||
yield return new ColumnGeometry(col, _superheader_column_index.ElementAtOrDefault(count), left, offset, right);
|
||||
yield return new ColumnGeometry() {
|
||||
column = col,
|
||||
super_column = _superheader_column_index.ElementAtOrDefault(count),
|
||||
unclipped_left = offset,
|
||||
left = left
|
||||
};
|
||||
}
|
||||
offset = right;
|
||||
offset += col.ActualWidth;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
@@ -1610,201 +1419,19 @@ namespace mbts.ui.controls {
|
||||
|
||||
public static IObservable<T> SubscribeMouseHoverRow<T>(this DIYGrid grid, IEqualityComparer<T> comparer) where T : class =>
|
||||
Observable.Create<T>(o => {
|
||||
T last_row = null;
|
||||
return grid.SubscribeMouseHoverRowContainer().Subscribe(row_view => {
|
||||
if (grid.RowContainerGenerator.ItemFromContainer(row_view) is T row && !comparer.Equals(row, last_row))
|
||||
o.OnNext(last_row = row);
|
||||
});
|
||||
});
|
||||
|
||||
public static IObservable<ListBoxItem> SubscribeMouseHoverRowContainer(this DIYGrid grid) =>
|
||||
Observable.Create<ListBoxItem>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
ListBoxItem curr_row = null;
|
||||
T curr_row = null;
|
||||
return Disposable.Create(
|
||||
grid.SubscribeMouseEnter(on_mouse),
|
||||
grid.SubscribeMouseLeave(on_mouse),
|
||||
grid.SubscribeMouseLeftButtonDown(on_mouse),
|
||||
grid.SubscribeMouseLeftButtonUp(on_mouse),
|
||||
grid.SubscribeMouseMove(on_mouse));
|
||||
void on_mouse(object sender, MouseEventArgs e) => refresh();
|
||||
grid.SubscribeMouseEnter(on_mouse_enter),
|
||||
grid.SubscribeMouseLeave(on_mouse_leave),
|
||||
grid.SubscribePreviewMouseMove(on_mouse_move));
|
||||
void on_mouse_enter(object sender, MouseEventArgs e) => refresh();
|
||||
void on_mouse_move(object sender, MouseEventArgs e) => refresh();
|
||||
void on_mouse_leave(object sender, MouseEventArgs e) => refresh();
|
||||
void refresh() {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
} else {
|
||||
dirty = false;
|
||||
busy = true;
|
||||
do {
|
||||
grid.Dispatcher.InvokeAsync(() => {
|
||||
var row = grid.ChildAtPoint<ListBoxItem>(Mouse.GetPosition(grid));
|
||||
if (row != curr_row)
|
||||
o.OnNext(curr_row = row);
|
||||
dirty = false;
|
||||
});
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
T row = grid.ChildAtPoint<ListBoxItem>(Mouse.GetPosition(grid)) is var rview && rview != null ? grid.RowContainerGenerator.ItemFromContainer(rview) as T : null;
|
||||
if (!comparer.Equals(curr_row, row))
|
||||
o.OnNext(curr_row = row);
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<DIYGrid.CellGeometry?> SubscribeMouseHoverCell(this DIYGrid grid) {
|
||||
return Observable.Create<DIYGrid.CellGeometry?>(o => {
|
||||
IDisposable row_mouse_hooks = null;
|
||||
DIYGrid.CellGeometry? last_value = null;
|
||||
var layout = grid.GetColumnGeometry().ToList();
|
||||
var layout_x_pts = layout.WrapSelector(cg => cg.left, null);
|
||||
ListBoxItem hover_row = null;
|
||||
grid.ColumnLayoutChanged += on_layout;
|
||||
var hover_sub = grid.SubscribeMouseHoverRowContainer().Subscribe(on_row_view);
|
||||
return Disposable.Create(dispose);
|
||||
//
|
||||
void notify(MouseEventArgs e = null) {
|
||||
if (hover_row == null || layout.Count == 0)
|
||||
on_cell(null);
|
||||
else {
|
||||
var pt = e?.GetPosition(hover_row) ?? Mouse.GetPosition(hover_row);
|
||||
int index = CollectionHelper.binary_search(layout_x_pts, pt.X);
|
||||
if (index < 0)
|
||||
index = ~index - 1;
|
||||
if (index >= 0 && index < layout.Count) {
|
||||
on_cell(new DIYGrid.CellGeometry(layout[index], hover_row));
|
||||
} else {
|
||||
on_cell(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_cell(DIYGrid.CellGeometry? cell) {
|
||||
if (!cell.Equals(last_value))
|
||||
o.OnNext(last_value = cell);
|
||||
}
|
||||
void on_row_view(ListBoxItem row_view) {
|
||||
row_mouse_hooks?.Dispose();
|
||||
hover_row = row_view;
|
||||
row_mouse_hooks = row_view?.SubscribeMouseMove(on_mouse_change);
|
||||
notify();
|
||||
}
|
||||
void on_layout(List<DIYGrid.ColumnGeometry> obj) {
|
||||
layout = grid.GetColumnGeometry().ToList();
|
||||
layout_x_pts = layout.WrapSelector(cg => cg.left, null);
|
||||
notify();
|
||||
}
|
||||
void on_mouse_change(object sender, MouseEventArgs e) =>
|
||||
notify(e);
|
||||
void dispose() {
|
||||
Disposable.dispose(ref hover_sub);
|
||||
grid.ColumnLayoutChanged -= on_layout;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IObservable<(IEnumerable<DIYGrid.ColumnGeometry> columns, int row_start, int row_count)?> SubscribeCellDragRect(this DIYGrid grid) {
|
||||
return Observable.Create<(IEnumerable<DIYGrid.ColumnGeometry> columns, int row_start, int row_count)?>(o => {
|
||||
return grid.ConnectOnLoaded(init);
|
||||
IDisposable init() {
|
||||
if (!( grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter) && presenter.try_find_descendent_of_type(out ScrollViewer scroll) && scroll.try_find_descendent_of_type(out ScrollContentPresenter scroll_content)))
|
||||
return null;
|
||||
// init state
|
||||
var value_container = new DistinctSubject<(IEnumerable<DIYGrid.ColumnGeometry> columns, int row_start, int row_count)?>();
|
||||
double y_start_drift = 0;
|
||||
var layout = new List<DIYGrid.ColumnGeometry>();
|
||||
var layout_x_pts = layout.WrapSelector(cg => cg.left, null);
|
||||
object _drag_start_item = null;
|
||||
int _drag_start_index = -1;
|
||||
Point? drag_start = null, drag_curr = null;
|
||||
//
|
||||
scroll.ScrollChanged += on_scroll;
|
||||
grid.ColumnLayoutChanged += on_layout;
|
||||
var drag_hook = DragDropHelper.subscribe_drag_coordinates(presenter, grid, use_preview_events: false, mouse_button: MouseButton.Right).Subscribe(pt => {
|
||||
if (drag_start.HasValue) {
|
||||
if (!pt.HasValue) {
|
||||
drag_start = null;
|
||||
drag_curr = null;
|
||||
} else {
|
||||
drag_curr = pt;
|
||||
}
|
||||
} else {
|
||||
if (pt.HasValue) {
|
||||
var item_container = presenter.ChildAtPoint<ListBoxItem>(pt.Value);
|
||||
if (item_container == null) {
|
||||
pt = null;
|
||||
} else {
|
||||
_drag_start_item = grid.RowContainerGenerator.ItemFromContainer(item_container);
|
||||
_drag_start_index = grid.RowContainerGenerator.IndexFromContainer(item_container);
|
||||
y_start_drift = scroll.VerticalOffset;
|
||||
}
|
||||
}
|
||||
drag_start = pt;
|
||||
drag_curr = pt;
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
grid.PreviewMouseUp += (s, e) => e.Handled = e.ChangedButton == MouseButton.Right;
|
||||
var sub = value_container.Subscribe(o);
|
||||
return Disposable.Create(dispose);
|
||||
void on_layout(List<DIYGrid.ColumnGeometry> new_layout) {
|
||||
layout = new_layout;
|
||||
layout_x_pts = layout.WrapSelector(cg => cg.left, null);
|
||||
}
|
||||
void on_scroll(object sender, ScrollChangedEventArgs e) {
|
||||
if (drag_start.HasValue)
|
||||
refresh();
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
if (!drag_start.HasValue) {
|
||||
value_container.OnNext(null);
|
||||
return;
|
||||
}
|
||||
var (xstart, ystart) = drag_start.Value.deconstruct();
|
||||
var (xstop, ystop) = drag_curr.Value.deconstruct();
|
||||
xstart = MathUtils.within_range(0, presenter.ActualWidth, xstart);
|
||||
var row_height = grid.RowHeight;
|
||||
xstop = MathUtils.within_range(0, scroll_content.RenderSize.Width - 1, xstop);
|
||||
ystop = MathUtils.within_range(0, scroll_content.RenderSize.Height - 1, ystop);
|
||||
double vertical_diff = ystop + (row_height * (scroll.VerticalOffset - y_start_drift)) - ystart;
|
||||
int start_index, n_items;
|
||||
if (vertical_diff > 0) {
|
||||
double first_item_partial_height = row_height - (ystart % row_height);
|
||||
n_items = (int)Math.Ceiling((vertical_diff - first_item_partial_height) / row_height) + 1;
|
||||
start_index = _drag_start_index;
|
||||
int overflow = start_index + n_items - presenter.Items.Count;
|
||||
if (overflow > 0)
|
||||
n_items -= overflow;
|
||||
} else {
|
||||
double first_item_partial_height = ystart % row_height;
|
||||
n_items = -(int)Math.Floor((vertical_diff + first_item_partial_height) / row_height) + 1;
|
||||
start_index = _drag_start_index - n_items + 1;
|
||||
if (start_index < 0) {
|
||||
n_items += start_index;
|
||||
start_index = 0;
|
||||
}
|
||||
}
|
||||
if (n_items > 0) {
|
||||
var colstart = find_col(xstart);
|
||||
var colstop = find_col(xstop);
|
||||
(colstart, colstop) = MathUtils.get_outliers(colstart, colstop);
|
||||
(ystart, ystop) = MathUtils.get_outliers(ystart, ystop);
|
||||
var cols = layout.WrapSubRange(colstart, colstop - colstart + 1);
|
||||
value_container.OnNext((cols, start_index, n_items));
|
||||
}
|
||||
}
|
||||
|
||||
int find_col(double x) {
|
||||
if (layout.Count == 0)
|
||||
return -1;
|
||||
int i = CollectionHelper.binary_search(layout, x, l => l.left);
|
||||
if (i < 0) {
|
||||
i = ~i - 1;
|
||||
}
|
||||
return MathUtils.within_range(0, layout.Count - 1, i);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
Disposable.dispose(ref sub);
|
||||
grid.ColumnLayoutChanged -= on_layout;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
class DIYGridPopupManager {
|
||||
readonly DIYGrid _grid;
|
||||
readonly FrameworkElement _owner;
|
||||
readonly ScrollContentPresenter _scroll_content;
|
||||
readonly ICollection<Visual> _children;
|
||||
Popup _active_popup;
|
||||
|
||||
public static DIYGridPopupManager create(FrameworkElement owner) =>
|
||||
create(owner, new VisualCollectionWrapper(new VisualCollection(owner)));
|
||||
|
||||
public static DIYGridPopupManager create(FrameworkElement owner, ICollection<Visual> visual_children) =>
|
||||
owner.try_find_descendent_of_type(out DIYGrid grid) &&
|
||||
grid.try_find_descendent_of_type(out DIYGridRowsPresenter rows_presenter) &&
|
||||
rows_presenter.try_find_descendent_of_type(out ScrollContentPresenter scroll_content) ?
|
||||
new DIYGridPopupManager(owner, grid, scroll_content, visual_children) :
|
||||
null;
|
||||
|
||||
DIYGridPopupManager(FrameworkElement owner, DIYGrid grid, ScrollContentPresenter scroll_content, ICollection<Visual> children) {
|
||||
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
|
||||
_grid = grid ?? throw new ArgumentNullException(nameof(grid));
|
||||
_scroll_content = scroll_content ?? throw new ArgumentNullException(nameof(scroll_content));
|
||||
_children = children;
|
||||
}
|
||||
|
||||
public Popup active_popup {
|
||||
get => _active_popup;
|
||||
set {
|
||||
if (_active_popup != value) {
|
||||
_active_popup = value;
|
||||
if (_active_popup != null) {
|
||||
_active_popup.IsOpen = false;
|
||||
_active_popup.IsOpen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void show(FrameworkElement view, int delay_measure_ms = 100) {
|
||||
var layout = _grid.GetColumnGeometry().ToList();
|
||||
if (layout.Count == 0)
|
||||
return;
|
||||
var x_origin = layout[0].unclipped_left;
|
||||
var mouse_pos = Mouse.GetPosition(_scroll_content);
|
||||
var col_index = CollectionHelper.binary_search(layout, mouse_pos.X + x_origin, cg => cg.left);
|
||||
if (col_index < 0)
|
||||
col_index = ~col_index - 1;
|
||||
col_index = MathUtils.within_range(0, layout.Count - 1, col_index);
|
||||
var col = layout[col_index];
|
||||
// determine top left
|
||||
var top_left = _scroll_content.PointToScreenPixels(
|
||||
col.unclipped_left - x_origin,
|
||||
Math.Ceiling(mouse_pos.Y / _grid.RowHeight) * _grid.RowHeight);
|
||||
if (delay_measure_ms > 0)
|
||||
await Task.Delay(delay_measure_ms);
|
||||
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
// popup
|
||||
var popup = new ZPopup {
|
||||
PlacementTarget = _grid,
|
||||
Placement = PlacementMode.Absolute,
|
||||
Child = view,
|
||||
IsOpen = true,
|
||||
HorizontalOffset = top_left.X,
|
||||
VerticalOffset = top_left.Y,
|
||||
StaysOpen = false
|
||||
};
|
||||
popup.Child.IsMouseCaptureWithinChanged += (s, e) => {
|
||||
if (!popup.StaysOpen) {
|
||||
var pt = Mouse.GetPosition(popup.Child);
|
||||
if (!popup.Child.IsWithinBounds(pt)) {
|
||||
popup.IsOpen = false;
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter pres))
|
||||
pres.CaptureMouse();
|
||||
}
|
||||
}
|
||||
};
|
||||
popup.SetBinding(WindowStyle.UnsafeWindowProperty, new Binding {
|
||||
Source = _owner,
|
||||
Path = new PropertyPath(WindowStyle.UnsafeWindowProperty)
|
||||
});
|
||||
view.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
|
||||
view.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => popup.IsOpen = false));
|
||||
|
||||
view.PreviewMouseDown += on_view_preview_mouse_down;
|
||||
var drag_hooks = PopupViewController.enable_drag_move(_owner, popup);
|
||||
_active_popup = popup;
|
||||
_children.Add(popup);
|
||||
_owner.ReleaseMouseCapture();
|
||||
popup.Closed += on_closed;
|
||||
FocusManager.SetIsFocusScope(view, true);
|
||||
view.Focus();
|
||||
//
|
||||
void on_closed(object sender, EventArgs e) {
|
||||
if (popup == _active_popup) {
|
||||
return;
|
||||
}
|
||||
view.PreviewMouseDown -= on_view_preview_mouse_down;
|
||||
Disposable.dispose(ref drag_hooks);
|
||||
popup.ReleaseMouseCapture();
|
||||
if (Mouse.Captured == _owner)
|
||||
_owner.ReleaseMouseCapture();
|
||||
popup.Closed -= on_closed;
|
||||
_children.Remove(popup);
|
||||
}
|
||||
|
||||
void on_view_preview_mouse_down(object sender, MouseButtonEventArgs e) =>
|
||||
active_popup = popup;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,6 @@
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="SelectionMode" Value="Extended"/>
|
||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
||||
<Setter Property="ColumnHeaderStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
static class DoubleUtil {
|
||||
public const int API_DECIMAL_PRECISION = 9;
|
||||
namespace mbts.ui.controls
|
||||
{
|
||||
internal class DoubleUtil
|
||||
{
|
||||
/* smallest such that 1.0+DBL_EPSILON != 1.0 */
|
||||
private const double EPSILON = 2.2204460492503131e-016;
|
||||
private const double BIG_EPSILON = EPSILON * 10;
|
||||
@@ -35,9 +36,6 @@ namespace mbts.ui.controls {
|
||||
(double.IsNaN(right) ? double.NaN : -right) :
|
||||
(double.IsNaN(right) ? left : left - right);
|
||||
|
||||
public static decimal round_to_decimal(double value, int decimals = API_DECIMAL_PRECISION) =>
|
||||
double.IsNaN(value) ? 0 : (decimal)Math.Round(value, decimals, MidpointRounding.AwayFromZero);
|
||||
|
||||
internal static bool AreClose(double v1, double v2) {
|
||||
if (v1 == v2) return true;
|
||||
double delta = Math.Abs(v1 - v2);
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public class HighPerformanceRow<TRowVM> : HighPerformanceRowBase where TRowVM : class, IRowVM {
|
||||
public class HighPerformanceRow<TRowVM, TColKey> : HighPerformanceRowBase where TRowVM : class, IRowVM {
|
||||
double _row_height;
|
||||
ObservableCollection<ColumnBase> _columns;
|
||||
List<ColumnGeometry> _column_layout = new List<ColumnGeometry>();
|
||||
Dictionary<string, ColumnGeometry> _column_layout_map = new Dictionary<string, ColumnGeometry>();
|
||||
VisualDictionary<string, FrameworkElement> _element_cells;
|
||||
Dictionary<TColKey, ColumnGeometry> _column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
|
||||
VisualDictionary<TColKey, FrameworkElement> _element_cells;
|
||||
RecycleBin<DIYGrid> _grid_backing;
|
||||
RecycleBin<ListBoxItem> _container_backing;
|
||||
TRowVM _row;
|
||||
@@ -39,7 +39,7 @@ namespace mbts.ui.controls {
|
||||
public HighPerformanceRow() : this(DefaultRowHeight) { }
|
||||
public HighPerformanceRow(double row_height) {
|
||||
_row_height = row_height;
|
||||
_element_cells = new VisualDictionary<string, FrameworkElement>(this);
|
||||
_element_cells = new VisualDictionary<TColKey, FrameworkElement>(this);
|
||||
_grid_backing = new RecycleBin<DIYGrid>(
|
||||
g => { g.ColumnLayoutChanged += _on_column_layout_changed; g.RowHeightChanged += _on_row_height_changed; },
|
||||
g => { g.ColumnLayoutChanged -= _on_column_layout_changed; g.RowHeightChanged -= _on_row_height_changed; });
|
||||
@@ -123,7 +123,7 @@ namespace mbts.ui.controls {
|
||||
protected override Size ArrangeOverride(Size finalSize) {
|
||||
foreach (var kv in _column_layout_map) {
|
||||
if (_element_cells.TryGetValue(kv.Key, out FrameworkElement ec))
|
||||
ec.Arrange(kv.Value.to_rect(_row_height));
|
||||
ec.Arrange(new Rect(new Point(kv.Value.left, 0), new Size(kv.Value.width, _row_height)));
|
||||
}
|
||||
return finalSize;
|
||||
}
|
||||
@@ -148,18 +148,16 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
void _add_columns(IList items) {
|
||||
foreach (ColumnBase col in items) {
|
||||
if (col is IFrameworkElementColumn el_col) {
|
||||
var cell_view = el_col.view_factory();
|
||||
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.id));
|
||||
_element_cells.Add(col.id, cell_view);
|
||||
}
|
||||
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>()) {
|
||||
var cell_view = col.view_factory();
|
||||
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.key));
|
||||
_element_cells.Add(col.key, cell_view);
|
||||
}
|
||||
}
|
||||
|
||||
void _remove_columns(IList items) {
|
||||
foreach (ColumnBase col in items)
|
||||
_element_cells.Remove(col.id);
|
||||
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>())
|
||||
_element_cells.Remove(col.key);
|
||||
}
|
||||
|
||||
void _on_column_layout_changed(List<ColumnGeometry> column_layout) {
|
||||
@@ -173,10 +171,10 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
void _set_column_layout_map() {
|
||||
_column_layout_map = new Dictionary<string, ColumnGeometry>();
|
||||
_column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
|
||||
foreach (var cg in _column_layout) {
|
||||
if (cg.column is IFrameworkElementColumn)
|
||||
_column_layout_map.Add(cg.column.id, cg);
|
||||
if (cg.column is FrameworkElementColumnBase<TColKey> elc)
|
||||
_column_layout_map.Add(elc.key, cg);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -53,7 +53,11 @@ namespace mbts.ui.controls {
|
||||
if (col.is_partially_obscured) {
|
||||
var obscured_width = col.left - col.unclipped_left;
|
||||
grid.scroll_horizontal(-obscured_width);
|
||||
return new ColumnGeometry(col.column, col.super_column, col.left, col.left, col.right);
|
||||
return new ColumnGeometry() {
|
||||
column = col.column,
|
||||
left = col.left,
|
||||
unclipped_left = col.left
|
||||
};
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -23,7 +24,7 @@ namespace mbts.ui.settings {
|
||||
|
||||
public static GridSettings Default =>
|
||||
new GridSettings(
|
||||
selection_highlight: FallbackValues.selection_highlight.rgb_color(),
|
||||
selection_highlight: new ColorOpacityProto(FallbackValues.selection_highlight.Color, FallbackValues.selection_highlight.Opacity),
|
||||
background: _default_background_color, //new ColorOpacityProto(FallbackValues.background_color.Color, FallbackValues.background_color.Opacity));
|
||||
enable_background_color: _default_enable_background_color);
|
||||
|
||||
@@ -44,10 +45,11 @@ namespace mbts.ui.settings {
|
||||
public static double font_size => _default_font_size;
|
||||
public static Color font_color => _default_font_color.rgb_color();
|
||||
public static FontWeight font_weight => FontWeight.FromOpenTypeWeight(_default_font_weight);
|
||||
public static Color selection_highlight => Colors.MediumSlateBlue.with(0x63);
|
||||
public static SolidColorBrush selection_highlight => DIYGrid.DefaultSelectionHighlight;
|
||||
public static uint background_color => _default_background;
|
||||
}
|
||||
|
||||
public static IValueConverter selection_highlight_converter => ValueConverter.create<GridSettings, SolidColorBrush>(gs => gs.selection_highlight.to_brush().frozen());
|
||||
public static IValueConverter font_family_converter => ValueConverter.create<GridSettings, FontFamily>(gs => new FontFamily(gs.font_family));
|
||||
public static IValueConverter font_weight_converter => ValueConverter.create<GridSettings, FontWeight>(gs => gs.font_weight);
|
||||
public static IValueConverter font_size_converter => ValueConverter.create<GridSettings, double>(gs => gs.font_size);
|
||||
@@ -69,29 +71,8 @@ namespace mbts.ui.settings {
|
||||
[DefaultValue(_default_font_color)]
|
||||
readonly uint _font_color;
|
||||
// selection highlight
|
||||
ColorOpacityProto _obsolete_selection_highlight_backing;
|
||||
uint _selection_highlight_backing;
|
||||
[ProtoMember(5)]
|
||||
ColorOpacityProto _obsolete_selection_highlight {
|
||||
get => _obsolete_selection_highlight_backing;
|
||||
set {
|
||||
_obsolete_selection_highlight_backing = value;
|
||||
// only overwrite non-obsolete selection highlight if it is 0 (assume that means 'unassigned').
|
||||
if (_selection_highlight_backing == 0) {
|
||||
var color = value.argb_color;
|
||||
// ensure reasonable opacity
|
||||
color.A = MathUtils.within_range(color.A,
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.min_alpha,
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.max_alpha);
|
||||
_selection_highlight_backing = color.rgb_color();
|
||||
}
|
||||
}
|
||||
}
|
||||
[ProtoMember(10)]
|
||||
uint _selection_highlight {
|
||||
get => _selection_highlight_backing;
|
||||
set => _selection_highlight_backing = value;
|
||||
}
|
||||
readonly ColorOpacityProto _selection_highlight;
|
||||
// columns
|
||||
[ProtoMember(6)]
|
||||
List<GridColumnSettings> _columns;
|
||||
@@ -120,7 +101,7 @@ namespace mbts.ui.settings {
|
||||
double font_size = _default_font_size,
|
||||
FontWeight font_weight = default,
|
||||
uint font_color = _default_font_color,
|
||||
uint selection_highlight = 0,
|
||||
ColorOpacityProto? selection_highlight = null,
|
||||
IEnumerable<GridColumnSettings> columns = null,
|
||||
IEnumerable<SortingCriteria> sorting_criterion = null,
|
||||
uint background = _default_background,
|
||||
@@ -130,7 +111,7 @@ namespace mbts.ui.settings {
|
||||
_font_size = font_size;
|
||||
_font_color = font_color;
|
||||
_font_weight = (ushort)font_weight.ToOpenTypeWeight();
|
||||
_selection_highlight = selection_highlight;
|
||||
_selection_highlight = selection_highlight ?? new ColorOpacityProto(0, 1);
|
||||
_columns = new List<GridColumnSettings>(columns.empty_if_null());
|
||||
_sorting_criterion = new List<SortingCriteria>(sorting_criterion.empty_if_null());
|
||||
_background = background;
|
||||
@@ -142,27 +123,29 @@ namespace mbts.ui.settings {
|
||||
public uint font_color => _font_color;
|
||||
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
|
||||
public Color background => _background.argb_color();
|
||||
public Color selection_highlight => _selection_highlight.argb_color();
|
||||
public ColorOpacityProto selection_highlight => _selection_highlight;
|
||||
public uint background_color => _background;
|
||||
public bool enable_background_color => _enable_background_color;
|
||||
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 ?? (_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));
|
||||
|
||||
public GridSettings with(
|
||||
IEnumerable<GridColumnSettings> columns = null,
|
||||
IEnumerable<SortingCriteria> sorting_criterion = null,
|
||||
string font_family = null,
|
||||
double font_size = double.NaN,
|
||||
uint? background = null,
|
||||
bool? enable_background_color = null,
|
||||
uint? selection_highlight = null) =>
|
||||
bool? enable_background_color = null) =>
|
||||
new GridSettings(
|
||||
font_family ?? _font_family,
|
||||
DoubleHelper.IsNaNOrInfinityOr(font_size, 0) ? _font_size : font_size,
|
||||
font_weight,
|
||||
_font_color,
|
||||
selection_highlight ?? _selection_highlight,
|
||||
_selection_highlight,
|
||||
columns ?? _columns,
|
||||
sorting_criterion ?? _sorting_criterion,
|
||||
background ?? _background,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using bts.collections;
|
||||
using bts.settings;
|
||||
using bts.utils;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -118,26 +116,4 @@ namespace mbts.ui.settings {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static class GridSettingsMigrationRules {
|
||||
public class SelectionHighlightIsTranslucent : ISettingsMigrationRule<GridSettings> {
|
||||
public const byte min_alpha = 30, max_alpha = 200;
|
||||
public static bool apply(GridSettings settings, out GridSettings new_settings) {
|
||||
if (settings != null) {
|
||||
var sl = settings.selection_highlight;
|
||||
var a = MathUtils.within_range(sl.A, (byte)30, (byte)200);
|
||||
if (sl.A != a) {
|
||||
sl.A = a;
|
||||
new_settings = settings.with(selection_highlight: sl.rgb_color());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ISettingsMigrationRule<GridSettings>.apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
apply(settings, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,7 @@ namespace mbts.ui {
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
readonly DelegateProperty<double> _font_size;
|
||||
readonly DelegateProperty<SolidColorBrush> _font_color, _background_color;
|
||||
readonly DelegateProperty<Color> _selection_highlight;
|
||||
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
|
||||
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
|
||||
readonly Func<TPredicateData> _predicate_data_factory;
|
||||
readonly Func<TPredicateData, IPredicate<TRowData>> _predicate_factory;
|
||||
@@ -77,7 +76,7 @@ namespace mbts.ui {
|
||||
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
|
||||
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
|
||||
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight, nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
|
||||
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
|
||||
}
|
||||
|
||||
@@ -89,7 +88,7 @@ namespace mbts.ui {
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public double font_size => _font_size.Value;
|
||||
public SolidColorBrush font_color => _font_color.Value;
|
||||
public Color selection_highlight => _selection_highlight.Value;
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
public SolidColorBrush background_color => _background_color.Value;
|
||||
|
||||
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace mbts.spark {
|
||||
foreach (IGrouping<TPredicateData, CellAnimation> g in animations)
|
||||
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
|
||||
}
|
||||
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.color.rgb_color(), col_settings,
|
||||
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
|
||||
background: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
|
||||
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
|
||||
//
|
||||
@@ -146,7 +146,7 @@ namespace mbts.spark {
|
||||
_font_family.set(new FontFamily(sd.general.font_family));
|
||||
_font_weight.set(sd.general.font_weight);
|
||||
font_color.load_from(sd.general.font_color.rgb_solid_brush());
|
||||
selection_highlight.color = sd.general.selection_highlight;
|
||||
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
|
||||
_show_background_color.set(sd.general.enable_background_color);
|
||||
if (sd.general.enable_background_color) {
|
||||
// note that when the OB is moved to allow background colors
|
||||
|
||||
Loaded 100 of 321 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user