Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f437d50041 | ||
|
|
c7bbcc1738 | ||
|
|
3bdde95b42 | ||
|
|
52e6be5d60 | ||
|
|
bda76d0234 | ||
|
|
7cb08ba90a | ||
|
|
ee6d05c737 | ||
|
|
62a5ee94e8 | ||
|
|
f6e12d1664 | ||
|
|
d022080b80 | ||
|
|
c2a5c278e9 | ||
|
|
f895e69aa9 | ||
|
|
7c050a9e10 | ||
|
|
38087d21d8 | ||
|
|
4b2e49c4aa | ||
|
|
e31c0a8112 | ||
|
|
876d6e0981 |
No files matched your search
@@ -1,3 +1,6 @@
|
||||
1.3.4
|
||||
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
|
||||
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
|
||||
+6
-1
@@ -1,8 +1,13 @@
|
||||
1.3.4
|
||||
- Significantly reduced client memory use
|
||||
- Improved OrderBook performance
|
||||
- Improved workspace load time
|
||||
|
||||
1.3.3
|
||||
- Reduced server memory usage
|
||||
|
||||
1.3.2
|
||||
- OrderBook performance improvements
|
||||
- Improved OrderBook performance
|
||||
- Fix some memory leaks when switching workspaces
|
||||
|
||||
1.3.1
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace mbts.spark {
|
||||
where T : struct =>
|
||||
new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T> {
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T>, INotifyPropertyChanged {
|
||||
readonly OrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly string _prop_name;
|
||||
@@ -58,6 +58,10 @@ namespace mbts.spark {
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
OrderBookCell<T> _current;
|
||||
|
||||
// Property changed handler to avoid WPF binding leak
|
||||
// https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
|
||||
public event PropertyChangedEventHandler PropertyChanged = (_,__) => { };
|
||||
|
||||
public OrderBookCellStore(
|
||||
OrderBookRowVM row,
|
||||
ICVOrderBookColumn column,
|
||||
|
||||
@@ -15,7 +15,7 @@ using OrderStatus = bts.api.orderman.OrderStatus;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM<OrderRecordTuple>, DIYGrid.IActionableRowVM {
|
||||
public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
//public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
@@ -31,29 +31,65 @@ namespace mbts.spark {
|
||||
OrderBookRowVM _parent;
|
||||
OrderBookVM.BranchFilterSource _branch_filter_source;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store;
|
||||
IOrderBookCellStore<uint> _security_key_store;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store;
|
||||
IOrderBookCellStore<Venue> _exchange_store;
|
||||
IOrderBookCellStore<string> _symbol_store;
|
||||
IOrderBookCellStore<uint> _order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _security_key_store_backing;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store_backing;
|
||||
IOrderBookCellStore<Venue> _exchange_store_backing;
|
||||
IOrderBookCellStore<string> _symbol_store_backing;
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store;
|
||||
IOrderBookCellStore<Side> _side_store;
|
||||
IOrderBookCellStore<string> _user_store;
|
||||
IOrderBookCellStore<string> _account_store;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store;
|
||||
IOrderBookCellStore<DateTime> _time_created_store;
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store_backing;
|
||||
IOrderBookCellStore<Side> _side_store_backing;
|
||||
IOrderBookCellStore<string> _user_store_backing;
|
||||
IOrderBookCellStore<string> _account_store_backing;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store_backing;
|
||||
IOrderBookCellStore<DateTime> _time_created_store_backing;
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store;
|
||||
IOrderBookCellStore<OrderStatus> _status_store;
|
||||
IOrderBookCellStore<double> _price_store;
|
||||
IOrderBookCellStore<uint> _quantity_store;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store;
|
||||
IOrderBookCellStore<double> _last_price_store;
|
||||
IOrderBookCellStore<double> _avg_price_store;
|
||||
IOrderBookCellStore<string> _last_msg_store;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store;
|
||||
IOrderBookCellStore<DateTime> _last_change_store_backing;
|
||||
IOrderBookCellStore<OrderStatus> _status_store_backing;
|
||||
IOrderBookCellStore<double> _price_store_backing;
|
||||
IOrderBookCellStore<uint> _quantity_store_backing;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store_backing;
|
||||
IOrderBookCellStore<double> _last_price_store_backing;
|
||||
IOrderBookCellStore<double> _avg_price_store_backing;
|
||||
IOrderBookCellStore<string> _last_msg_store_backing;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store_backing;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store => _order_key_store_backing ?? (_order_key_store_backing = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store));
|
||||
IOrderBookCellStore<uint> _parent_order_key_store => _parent_order_key_store_backing ?? (_parent_order_key_store_backing = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store));
|
||||
IOrderBookCellStore<uint> _security_key_store => _security_key_store_backing ?? (_security_key_store_backing = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store));
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store => _security_store_backing ?? (_security_store_backing = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()));
|
||||
IOrderBookCellStore<Venue> _exchange_store => _exchange_store_backing ?? (_exchange_store_backing = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store));
|
||||
IOrderBookCellStore<string> _symbol_store => _symbol_store_backing ?? (_symbol_store_backing = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"));
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store => _order_type_store_backing ?? (_order_type_store_backing = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store));
|
||||
IOrderBookCellStore<Side> _side_store => _side_store_backing ?? (_side_store_backing = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store));
|
||||
IOrderBookCellStore<string> _user_store => _user_store_backing ?? (_user_store_backing = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store));
|
||||
IOrderBookCellStore<string> _account_store => _account_store_backing ?? (_account_store_backing = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store));
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store => _time_in_force_store_backing ?? (_time_in_force_store_backing = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store));
|
||||
IOrderBookCellStore<DateTime> _time_created_store => _time_created_store_backing ?? (_time_created_store_backing = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date));
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store => _last_change_store_backing ?? (_last_change_store_backing = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date));
|
||||
IOrderBookCellStore<OrderStatus> _status_store => _status_store_backing ?? (_status_store_backing = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store));
|
||||
IOrderBookCellStore<double> _price_store => _price_store_backing ?? (_price_store_backing = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price));
|
||||
IOrderBookCellStore<uint> _quantity_store => _quantity_store_backing ?? (_quantity_store_backing = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store));
|
||||
IOrderBookCellStore<uint> _filled_quantity_store => _filled_quantity_store_backing ?? (_filled_quantity_store_backing = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store));
|
||||
IOrderBookCellStore<double> _last_price_store => _last_price_store_backing ?? (_last_price_store_backing = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price));
|
||||
IOrderBookCellStore<double> _avg_price_store => _avg_price_store_backing ?? (_avg_price_store_backing = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price));
|
||||
IOrderBookCellStore<string> _last_msg_store => _last_msg_store_backing ?? (_last_msg_store_backing = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store));
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store => _order_sub_type_store_backing ?? (_order_sub_type_store_backing = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type));
|
||||
|
||||
static string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_price(double v) => _security.HasValue ?
|
||||
_security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
static string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public OrderBookRowVM(IBus bus, OrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityMD? s) {
|
||||
_book_vm = source_vm;
|
||||
@@ -61,43 +97,7 @@ namespace mbts.spark {
|
||||
_order = record;
|
||||
_security_md = s;
|
||||
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
|
||||
animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
// cells
|
||||
cell_stores = new DIYGrid.IGridCellStore[] {
|
||||
_security_store = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()),
|
||||
_security_key_store = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store),
|
||||
_parent_order_key_store = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store),
|
||||
_order_key_store = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store),
|
||||
_symbol_store = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"),
|
||||
_time_created_store = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date),
|
||||
_user_store = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store),
|
||||
_account_store = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store),
|
||||
_exchange_store = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store),
|
||||
_time_in_force_store = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store),
|
||||
_order_type_store = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store),
|
||||
_order_sub_type_store = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type),
|
||||
_side_store = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store),
|
||||
_last_change_store = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date),
|
||||
_price_store = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price),
|
||||
_quantity_store = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store),
|
||||
_filled_quantity_store = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store),
|
||||
_avg_price_store = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price),
|
||||
_last_price_store = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price),
|
||||
_status_store = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store),
|
||||
_last_msg_store = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store),
|
||||
};
|
||||
//
|
||||
string format_price(double v) => _security.HasValue ? _security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
//
|
||||
//animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
_visible = backing(nameof(is_visible), true);
|
||||
_expanded = backing(nameof(is_expanded), false);
|
||||
_depth_level = backing(nameof(depth_level), () => {
|
||||
@@ -212,7 +212,31 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores { get; }
|
||||
|
||||
DIYGrid.IGridCellStore[] _cell_stores_backing;
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => _cell_stores_backing ?? (_cell_stores_backing = new DIYGrid.IGridCellStore[] {
|
||||
_security_store,
|
||||
_security_key_store,
|
||||
_parent_order_key_store,
|
||||
_order_key_store,
|
||||
_symbol_store,
|
||||
_time_created_store,
|
||||
_user_store,
|
||||
_account_store,
|
||||
_exchange_store,
|
||||
_time_in_force_store,
|
||||
_order_type_store,
|
||||
_order_sub_type_store,
|
||||
_side_store,
|
||||
_last_change_store,
|
||||
_price_store,
|
||||
_quantity_store,
|
||||
_filled_quantity_store,
|
||||
_avg_price_store,
|
||||
_last_price_store,
|
||||
_status_store,
|
||||
_last_msg_store
|
||||
});
|
||||
|
||||
public static Stack<OrderBookRowVM> lineage(OrderBookRowVM node) {
|
||||
var rv = new Stack<OrderBookRowVM>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
@@ -69,12 +70,12 @@ namespace mbts.spark {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.OnDispatcher().AddRemoveOnly().Subscribe(on_orders),
|
||||
model.on_orders.OnDispatcher().Subscribe(on_orders),
|
||||
grid_settings_manager,
|
||||
columns,
|
||||
order_securities_subject);
|
||||
|
||||
void on_orders(LegacyTableUpdate<OrderRecordImmutable, uint> table) {
|
||||
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
|
||||
// clear everything (most likely the server restarted)
|
||||
if (table.removed.Count() == _order_rows.Count && !table.added.Any()) {
|
||||
_selected_rows.Clear();
|
||||
@@ -85,18 +86,14 @@ namespace mbts.spark {
|
||||
_parent_to_children.clear();
|
||||
_security_orders.clear();
|
||||
missing_security_orders.clear();
|
||||
Log.Ui.warn("OrderBook received reset message.");
|
||||
return;
|
||||
}
|
||||
var changed_keys = new HashSet<uint>();
|
||||
var added_securities = new List<SecurityImmutable>(1);
|
||||
var removed_securities = new List<SecurityImmutable>(1);
|
||||
|
||||
foreach (var o in table.removed) {
|
||||
// if state has it, then it wasn't actually removed, just changed
|
||||
if (table.state.TryGetValue(o.Key, out OrderRecordImmutable changed_order)) {
|
||||
changed_keys.Add(o.Key);
|
||||
_order_rows_cache[o.Key].update_order(changed_order);
|
||||
} else if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
row.Dispose();
|
||||
_order_rows.Remove(row);
|
||||
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
|
||||
@@ -123,10 +120,10 @@ namespace mbts.spark {
|
||||
missing_security_orders.remove(o.Key);
|
||||
}
|
||||
}
|
||||
foreach (var ch in table.changes)
|
||||
_order_rows_cache[ch.New.Key].update_order(ch.New);
|
||||
var orphans = new Dictionary<uint, OrderBookRowVM>();
|
||||
foreach (var o in table.added) {
|
||||
if (changed_keys.Contains(o.Key))
|
||||
continue;
|
||||
SecurityMD? security_md;
|
||||
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
|
||||
security_md = smd;
|
||||
|
||||
@@ -15,11 +15,9 @@ namespace mbts.spark {
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public const double RowHeight = 17;
|
||||
OrderBookVM _vm;
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
_vm = vm;
|
||||
DataContext = vm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using mbts.collections.extensions;
|
||||
using System.Collections;
|
||||
using bts.collections;
|
||||
|
||||
namespace mbts.ui.utility
|
||||
{
|
||||
|
||||
@@ -666,44 +666,6 @@ namespace mbts.collections.extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
#region RemoveWhere
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
|
||||
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
|
||||
int count = list.Count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
var t = list[i];
|
||||
if (where(t)) {
|
||||
list.RemoveAt(i--);
|
||||
--count;
|
||||
on_remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rng = new Random();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list) {
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
#region GetValueOrDefault
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace mbts.ui.settings
|
||||
}
|
||||
|
||||
public Task<IShellWindow> restore(WindowState window) {
|
||||
Log.Ui.info("Loading window '{0}'".fmt(window.title));
|
||||
bool changed;
|
||||
int count = 0;
|
||||
do {
|
||||
|
||||
@@ -446,9 +446,6 @@ namespace mbts.ui.shell
|
||||
public IEnumerable<string> AllErrors { get { return _errors.Values; } }
|
||||
#endregion
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, bool validate_now = false, params Func<T, bool>[] validation_steps) =>
|
||||
backing(name, default_value, validation_steps.Select(v => new PropertyValidation<T>(v)).ToLArray(), validate_now);
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, IEnumerable<PropertyValidation<T>> validation_steps, bool validate_now = false) {
|
||||
void run_validation(T val) {
|
||||
foreach (var pv in validation_steps) {
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace mbts.ui.controls {
|
||||
o.Resources[ActiveSelectionHighlightKey] = v;
|
||||
o.Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(v.Color) { Opacity = 0.2 }.frozen();
|
||||
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_brush().frozen(), 1).frozen();
|
||||
foreach (var col in o.Columns.OfType<ISelectionHighlightListener>())
|
||||
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
|
||||
col.on_selection_fill(v);
|
||||
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
|
||||
DefaultSelectionHighlight);
|
||||
@@ -634,7 +634,7 @@ namespace mbts.ui.controls {
|
||||
(o, v) => {
|
||||
if (DoubleHelper.IsNaNOrInfinity(v))
|
||||
v = 0;
|
||||
if (!o._visible_columns.is_null_or_empty()) {
|
||||
if (!o._visible_columns.is_null_or_empty() && o.Columns != null) {
|
||||
double max_font_size = o.Columns.Max(c => c.font_size);
|
||||
double min_row_height = o.RowPadding.Top + max_font_size + o.RowPadding.Bottom;
|
||||
if (v < min_row_height)
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace mbts.ui {
|
||||
readonly ObservableCollection<TRow> _rows;
|
||||
readonly ICollectionView _rows_view;
|
||||
readonly ObservableCollection<ColumnBase> _columns;
|
||||
readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
//readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
@@ -59,18 +59,18 @@ namespace mbts.ui {
|
||||
_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.to_brush().frozen(), nameof(grid_settings));
|
||||
_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
//_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
_resources = new List<IDisposable> {
|
||||
ObservableAutoWrapper.connect<SortDescriptionCollection, SortDescription, string>(
|
||||
rows_view.SortDescriptions, live_sorting_props = ((ICollectionViewLiveShaping)rows_view).LiveSortingProperties, sd => sd.PropertyName),
|
||||
(launch_settings_editor = new DialogCommand(create_settings_view))
|
||||
};
|
||||
//
|
||||
PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
pa.set_predicate_animations(_animation_defs);
|
||||
return pa;
|
||||
}
|
||||
//PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
// var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
// pa.set_predicate_animations(_animation_defs);
|
||||
// return pa;
|
||||
//}
|
||||
Window create_settings_view() {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateBuilder>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm);
|
||||
// need to refresh before launching the settings-view bc users can chanve some grid-column-settings properties directly from the grid (e.g. width, is_collapsed, etc.)
|
||||
@@ -134,9 +134,9 @@ namespace mbts.ui {
|
||||
_animation_defs.add(pa.predicate_builder.create_predicate(_arg), PredicateAnimations.flat_definitions<TRow, TRowData>(pa.row_animations, pa.cell_animations, lookup_column));
|
||||
}
|
||||
}
|
||||
foreach (var a in _animators) {
|
||||
a.set_predicate_animations(_animation_defs);
|
||||
}
|
||||
//foreach (var a in _animators) {
|
||||
// a.set_predicate_animations(_animation_defs);
|
||||
//}
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null && _columns != null) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -14,9 +15,10 @@ namespace mbts.ui.controls {
|
||||
SetCurrentValue(FocusableProperty, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
|
||||
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
// ObservableCollection to avoid WPF Binding Leak
|
||||
ItemsSource = new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)));
|
||||
|
||||
public bool ShowText {
|
||||
get { return (bool)GetValue(ShowTextProperty); }
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Windows.Interop;
|
||||
using System.Runtime.InteropServices;
|
||||
using mbts.ui.native;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
@@ -82,7 +83,14 @@ namespace mbts.ui.shell
|
||||
return _hwnd;
|
||||
}
|
||||
|
||||
protected override void DestroyWindowCore(HandleRef hwnd) { }
|
||||
protected override void DestroyWindowCore(HandleRef hwnd) {
|
||||
// Improve GC performance by keeping windows out of the Finalizer queue
|
||||
if (hwnd.Wrapper is WPFPanel p) {
|
||||
p.BeginInvoke((Action)(() => {
|
||||
p.Dispose();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError=true)]
|
||||
static extern IntPtr SetParent(HandleRef hwndChild, HandleRef hwndParent);
|
||||
|
||||
@@ -222,16 +222,15 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
if (def.windows != null) {
|
||||
foreach (var window in def.windows) {
|
||||
Log.Ui.info("Loading window '{0}'".fmt(window.title));
|
||||
var new_shell_window = await _window_restore.restore(window);
|
||||
// user can close the launcher from the taskbar while a workspace is loading
|
||||
if (_closing) {
|
||||
return;
|
||||
} else if (new_shell_window != null) {
|
||||
await _wm.add(new_shell_window);
|
||||
async Task restore(WindowState window) {
|
||||
var w = await _window_restore.restore(window);
|
||||
if (!_closing) {
|
||||
await _wm.add(w);
|
||||
}
|
||||
}
|
||||
var loading = def.windows.Select(restore);
|
||||
await Task.WhenAll(loading);
|
||||
if (_closing) return;
|
||||
}
|
||||
sw.Stop();
|
||||
Log.Ui.info($"Time to load workspace '{name}': {sw.Elapsed}");
|
||||
|
||||
@@ -16,6 +16,7 @@ using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using mbts.ui;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace bts.view {
|
||||
public partial class SparkWindowView : UserControl {
|
||||
@@ -36,7 +37,8 @@ namespace bts.view {
|
||||
_menu.ShowText = false;
|
||||
_grid.Children.Add(_menu);
|
||||
if (ContextMenu != null) {
|
||||
ContextMenu.ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
// ObservableCollection to avoid WPF Binding Leak
|
||||
ContextMenu.ItemsSource = new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)));
|
||||
}
|
||||
} else {
|
||||
expand_button.Visibility = Visibility.Collapsed;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.collections {
|
||||
public static class ListExtensions {
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
|
||||
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
|
||||
int count = list.Count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
var t = list[i];
|
||||
if (where(t)) {
|
||||
list.RemoveAt(i--);
|
||||
--count;
|
||||
on_remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rng = new Random();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list) {
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace bts.collections {
|
||||
|
||||
// Insert (return: true) or overwrite (return false) a new value
|
||||
// NOTE: you should use AddAll if you are adding multiple values
|
||||
bool Insert(T item) {
|
||||
public bool Insert(T item) {
|
||||
var item_id = id(item);
|
||||
if (!_index.TryGetValue(item_id, out Node itemnode)) {
|
||||
// Item is new
|
||||
@@ -57,7 +57,7 @@ namespace bts.collections {
|
||||
}
|
||||
}
|
||||
}
|
||||
void Remove(T item) {
|
||||
public void Remove(T item) {
|
||||
var item_id = id(item);
|
||||
if (_index.TryGetValue(item_id, out Node itemnode)) {
|
||||
if (_index.TryGetValue(parent(item), out Node parentnode)) {
|
||||
@@ -75,7 +75,7 @@ namespace bts.collections {
|
||||
}
|
||||
|
||||
// Note: This method does not handle replacements correctly.
|
||||
void AddAll(IEnumerable<T> items) {
|
||||
public void AddAll(IEnumerable<T> items) {
|
||||
// When adding multiple items we build the index first to avoid the children before parents problem
|
||||
foreach (var item in items) {
|
||||
//_index[id(item)] = new Node(item, _comparer);
|
||||
@@ -103,6 +103,27 @@ namespace bts.collections {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node parentmost(T item) {
|
||||
if (_index.TryGetValue(parent(item), out Node pnode)) {
|
||||
return parentmost(pnode.value);
|
||||
}
|
||||
else if (_index.TryGetValue(id(item), out Node mynode)) {
|
||||
return mynode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<T> EnumerateGroup(T item) {
|
||||
var pnode = parentmost(item);
|
||||
if (pnode != null) {
|
||||
yield return pnode.value;
|
||||
foreach (var child in iterate(pnode.children)) {
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
public IEnumerator<T> GetEnumerator() {
|
||||
return iterate(_tree).GetEnumerator();
|
||||
}
|
||||
|
||||
+27
-3
@@ -1,6 +1,5 @@
|
||||
using bts.collections;
|
||||
using mbts.collections.extensions;
|
||||
using NUnit.Framework;
|
||||
using bts.test;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,7 +8,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.tests {
|
||||
[TestFixture]
|
||||
[TestGroup]
|
||||
class SortedTreeTests {
|
||||
|
||||
class Order {
|
||||
@@ -92,5 +91,30 @@ namespace bts.tests {
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_enumerate_group() {
|
||||
var comparer = Comparer<Order>.Create((l, r) => l.name.CompareTo(r.name));
|
||||
var input = orders.ToList();
|
||||
input.Shuffle();
|
||||
var tree = new SortedTree<Order, int>(comparer, id, pid, input);
|
||||
Assert.SequenceEqual(tree, orders);
|
||||
|
||||
var children_of_a = tree.EnumerateGroup(orders.First(o => o.name == 'A'));
|
||||
var also_children_of_a = tree.EnumerateGroup(orders.First(o => o.name == 'C'));
|
||||
Assert.AreEqual(3, children_of_a.Count());
|
||||
Assert.AreEqual(3, also_children_of_a.Count());
|
||||
Assert.SequenceEqual(children_of_a, also_children_of_a);
|
||||
|
||||
var children_of_d = tree.EnumerateGroup(orders.First(o => o.name == 'F'));
|
||||
var also_children_of_d = tree.EnumerateGroup(orders.First(o => o.name == 'E'));
|
||||
Assert.AreEqual(children_of_d.First().name, 'D');
|
||||
Assert.AreEqual(also_children_of_d.First().name, 'D');
|
||||
Assert.SequenceEqual(children_of_d, also_children_of_d);
|
||||
Assert.SequenceEqual(children_of_d.Select(o => o.name), new [] { 'D', 'E', 'F' });
|
||||
|
||||
var children_of_g = tree.EnumerateGroup(orders.First(o => o.name == 'G'));
|
||||
Assert.SequenceEqual(children_of_g.Select(o => o.name), new [] { 'G' });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,17 @@ Logging {
|
||||
}
|
||||
}
|
||||
|
||||
Logging {
|
||||
rule: "System.platform == 'windows'"
|
||||
value {
|
||||
console { default_level: "Error" }
|
||||
syslog { default_level: "Info" }
|
||||
file { default_level: "Info" }
|
||||
initial_queue_size: 1024
|
||||
max_queue_growth: 100
|
||||
}
|
||||
}
|
||||
|
||||
Logging
|
||||
{
|
||||
value
|
||||
|
||||
Reference in new issue
Block a user