Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1ed33c2ee | ||
|
|
8bc1455813 | ||
|
|
5a36af1ed8 | ||
|
|
bc12a3bd42 | ||
|
|
4e831b5f89 | ||
|
|
d886327c1a | ||
|
|
ecaa04ecf8 | ||
|
|
44e3070544 | ||
|
|
1815950835 | ||
|
|
533ff8acf7 | ||
|
|
e64eaec240 | ||
|
|
02a30142b7 | ||
|
|
3937939c0a | ||
|
|
8762fbf7ce | ||
|
|
0459171d6e | ||
|
|
be4bfb6152 | ||
|
|
fcdf72c5d9 | ||
|
|
f5caf0b7ba | ||
|
|
4ff1be11e2 | ||
|
|
dad4c1bf9b | ||
|
|
40aa6c518a | ||
|
|
9f575fcdec | ||
|
|
a0f2673b3a | ||
|
|
a807a09bea | ||
|
|
ba3439b049 | ||
|
|
ebe29fec00 | ||
|
|
aee344fc2c | ||
|
|
bd451f6f65 | ||
|
|
7476b40bf1 | ||
|
|
c5cf90017e | ||
|
|
c142ce2a8e | ||
|
|
d082c47e49 | ||
|
|
44d0b96f25 | ||
|
|
cdb8bccb5a | ||
|
|
0f355dfa8f | ||
|
|
47cb8e0b04 | ||
|
|
ae4d7f719b | ||
|
|
a75784ca02 | ||
|
|
0e35ab3a52 | ||
|
|
1bb5ea8aaa | ||
|
|
64916f6bfc | ||
|
|
1a9dd1c37a | ||
|
|
d77925c568 | ||
|
|
aa62338401 | ||
|
|
ef5dc90406 | ||
|
|
273145feaf | ||
|
|
fe71b3081c | ||
|
|
8000ccb595 | ||
|
|
b9297a86a9 | ||
|
|
a68926bb4a | ||
|
|
f4eb427241 | ||
|
|
4c9d7ef54a | ||
|
|
359a9be900 | ||
|
|
6430f6e2df | ||
|
|
adac484f25 | ||
|
|
c1f7b030ba | ||
|
|
a363421de0 | ||
|
|
7428f9dbfb | ||
|
|
72e35a5914 | ||
|
|
158d63383f | ||
|
|
465b7f8bdf | ||
|
|
8c58454e32 | ||
|
|
feab43ee3f | ||
|
|
15fb97c688 | ||
|
|
dc922a0299 | ||
|
|
729acc4ad5 | ||
|
|
4d559b8ec1 | ||
|
|
2561cb3960 | ||
|
|
c4ba25bdae | ||
|
|
f1f1b68ffb | ||
|
|
835f453f03 | ||
|
|
0eb9518485 | ||
|
|
b5c70fc34e | ||
|
|
a1dba1d7d0 | ||
|
|
efdea5256f | ||
|
|
7563eb7afe | ||
|
|
5df1e5f383 | ||
|
|
99750da31d | ||
|
|
49247dba3f | ||
|
|
a25257cb33 | ||
|
|
b1c180d663 | ||
|
|
6afb16abcb | ||
|
|
995fa3df35 | ||
|
|
18eb28dfd2 | ||
|
|
99cdfadf8d | ||
|
|
1b4275db4c | ||
|
|
592ad39a32 | ||
|
|
1fd66a17ca | ||
|
|
3ec896d609 | ||
|
|
8f1eab57b7 |
No files matched your search
Vendored
+4
-5
@@ -44,7 +44,6 @@
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/build/ninja_debug/sdk/examples" : "${workspaceRoot}/src/sdk/examples",
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
@@ -105,7 +104,7 @@
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -119,7 +118,7 @@
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"test_filter"
|
||||
"ApiUpdateTests"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -136,13 +135,13 @@
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
|
||||
@@ -59,7 +59,6 @@ else ()
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/excel_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
|
||||
@@ -53,3 +53,25 @@ If exposed_qty is set to 0 the splitter will continue to exist in the Order Book
|
||||
##### Parameters
|
||||
* exposed_qty - The total quantity to expose on the market.
|
||||
* max_order_qty - The maximum size of a single order.
|
||||
|
||||
|
||||
## Scratch
|
||||
The scratch algo is similar to Lean, but instead of watching the opposite inside market, it watches the same side market and pays up when this quantity recedes below the scratch_threshold
|
||||
|
||||
##### Parameters
|
||||
* scratch_threshold - The quantity on the same side market below which to scratch
|
||||
* chase_ticks - The number of ticks through the opposite market to pay up
|
||||
|
||||
|
||||
## ProRata
|
||||
The prorata algo is a special case hedging algo for pro-rata markets. Users can specify two hedge types-- normal hedge and overfill hedge, the latter is spawned when you receive more fills than your fill_limit. Further, the algo attempts to scratch when in an overfilled state AND optionally the same side size falls below stay_in_threshold
|
||||
|
||||
##### Parameters
|
||||
* hedge_ticks - The amount of ticks better hedges are placed on the opposite side market as the fill
|
||||
* chase_ticks - How many ticks through the opposite market on a scratch
|
||||
* fill_limit - The limit after which the algo considers further fills to be 'overfills'
|
||||
* stay_in_threshold - A same side size threshold that enables the algo to 'stay in' even if overfilled
|
||||
* enable_scratch_on_overfill - When true, overfills are immediately scratched upon the algo exiting
|
||||
* enable_stay_in - When true, the algo will 'stay in' so long as the same side size is above stay_in_threshold, when false the algo attempts to 'get out' (and potentially scratch overfills) as soon as it reaches overfill status
|
||||
* hedge - Child order type to send for normal hedges
|
||||
* overfill_hedge - Child order type to send for overfill hedges
|
||||
@@ -4,6 +4,23 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.8.0
|
||||
### User Visible Changes
|
||||
- Added ProRata and Scratch algos
|
||||
- Ladder badge now grows/shrinks to fill the containing cell
|
||||
- Attached algo parameters are now displayed in the update panel
|
||||
- Fixed a bug where the update panel would show '-' values when only
|
||||
1 order was selected
|
||||
- Fixed a bug that cause cause a failure on startup if the user
|
||||
loaded a workspace with a Ladder that had a dynamic order type
|
||||
selected by default
|
||||
- Increased width of login panel to better show login error messages
|
||||
- Added optional 'Maker/Taker' pricing to Spreader algo
|
||||
### Internal changes
|
||||
- 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.
|
||||
@@ -347,7 +364,8 @@ insert_anchor_links = "right"
|
||||
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
|
||||
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
|
||||
- Zoom In = CTRL + Mouse wheel up
|
||||
- Zoom Out = CTRL + Mouse wheel down␍
|
||||
- Zoom Out = CTRL + Mouse wheel down
|
||||
|
||||
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
|
||||
### Exchange simulator
|
||||
- Improved the exchange simulator to track real market data more closely
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.api.persistence;
|
||||
using bts.api.position;
|
||||
using bts.api.synthetic;
|
||||
@@ -45,41 +46,11 @@ namespace Spark {
|
||||
|
||||
[Export]
|
||||
public MarketDataRelay md_replay => context.locate<MarketDataRelay>();
|
||||
//[Export]
|
||||
//public AlgoService algo_service => context.locate<AlgoService>();
|
||||
|
||||
AlgoServiceWrapper _algo_service_wrapper;
|
||||
[Export]
|
||||
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
|
||||
public AlgoDefinitionService algo_definition_service => context.locate<AlgoDefinitionService>();
|
||||
|
||||
[Export]
|
||||
public MBOService mbo_service => context.locate<MBOService>();
|
||||
}
|
||||
|
||||
class AlgoServiceWrapper {
|
||||
public enum Mode {
|
||||
None,
|
||||
Hierarchical,
|
||||
Flat
|
||||
}
|
||||
|
||||
public AlgoServiceWrapper(ServiceContext context) {
|
||||
var flat_model_options = mbts.config.Configuration.get<bts.config.AlgoStudioFlatDefinitionModelOptions>();
|
||||
if (flat_model_options.enabled) {
|
||||
flat = context.locate<bts.api.orderman.algo.AlgoDefinitionService>();
|
||||
} else {
|
||||
hierarhical = context.locate<AlgoService>();
|
||||
}
|
||||
}
|
||||
|
||||
public AlgoService hierarhical { get; }
|
||||
public bts.api.orderman.algo.AlgoDefinitionService flat { get; }
|
||||
public Mode mode {
|
||||
get {
|
||||
if (hierarhical != null) return Mode.Hierarchical;
|
||||
if (flat != null) return Mode.Flat;
|
||||
return Mode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,6 +374,72 @@
|
||||
Height="{TemplateBinding Height}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<Border.Resources>
|
||||
<!--Leg Count States-->
|
||||
<sys:String x:Key="LegCountStates">LegCountStates</sys:String>
|
||||
<sys:String x:Key="LegCountStates.TwoOrFewer">TwoOrFewerLegsState</sys:String>
|
||||
<sys:String x:Key="LegCountStates.ThreeOrMore">ThreeOrMoreLegsState</sys:String>
|
||||
<cv:ValueConverterPipe x:Key="LegCountStateConverter">
|
||||
<cv:IntGreaterThanConverter Value="2"/>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{StaticResource LegCountStates.TwoOrFewer}" ValueForTrue="{StaticResource LegCountStates.ThreeOrMore}"/>
|
||||
</cv:ValueConverterPipe>
|
||||
<!--Persistance States-->
|
||||
<sys:String x:Key="PersistanceStates">PersistanceStates</sys:String>
|
||||
<sys:String x:Key="PersistanceStates.Unpersisted">UnpersistedState</sys:String>
|
||||
<sys:String x:Key="PersistanceStates.Persisted">PersistedState</sys:String>
|
||||
<cv:BooleanToObjectConverter x:Key="PersistanceStateConverter" ValueForFalse="{StaticResource PersistanceStates.Persisted}" ValueForTrue="{StaticResource PersistanceStates.Unpersisted}"/>
|
||||
</Border.Resources>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.LegTuples.Count, RelativeSource={RelativeSource Self}, Converter={StaticResource LegCountStateConverter}}"/>
|
||||
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.IsNew, RelativeSource={RelativeSource Self}, Converter={StaticResource PersistanceStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{StaticResource LegCountStates}">
|
||||
<VisualState Name="{StaticResource LegCountStates.TwoOrFewer}"/>
|
||||
<VisualState Name="{StaticResource LegCountStates.ThreeOrMore}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_Label" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_DropDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{StaticResource PersistanceStates}">
|
||||
<VisualState Name="{StaticResource PersistanceStates.Unpersisted}"/>
|
||||
<VisualState Name="{StaticResource PersistanceStates.Persisted}">
|
||||
<Storyboard>
|
||||
<!--Name-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_TextBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<!--Tick Factors-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Picker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<!--Pricing-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_DropDown" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -388,8 +454,9 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
|
||||
<TextBlock x:Name="PART_Name_Label" VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
|
||||
<TextBox
|
||||
x:Name="PART_Name_TextBox"
|
||||
Grid.Column="1"
|
||||
@@ -408,20 +475,37 @@
|
||||
</u:Behavior.BindingUpdateGroups>
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
x:Name="PART_TickFactors_Label"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks"
|
||||
IsEnabled="True"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
x:Name="PART_TickFactors_Picker"
|
||||
Grid.Column="3"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="True"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
|
||||
<TextBlock
|
||||
x:Name="PART_Pricing_Label"
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
IsEnabled="True"
|
||||
Text="Pricing"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_Pricing_DropDown"
|
||||
Grid.Column="5"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Width="90"
|
||||
IsEnabled="{TemplateBinding IsNew}"
|
||||
IsEnabled="True"
|
||||
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
|
||||
SelectedItem="{Binding price_calc}"
|
||||
>
|
||||
@@ -431,6 +515,24 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_HedgePricing_Label"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Hedge"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_HedgePricing_DropDown"
|
||||
Grid.Column="7"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
ItemsSource="{TemplateBinding HedgePricingItemsSource}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="PART_DataGrid"
|
||||
|
||||
@@ -8,6 +8,7 @@ using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -183,6 +184,29 @@ namespace mbts.ui.spark {
|
||||
public mbts.spark.ContractPickerModel ContractPickerModel { get => GetValue(ContractPickerModelProperty) as mbts.spark.ContractPickerModel; set => SetValue(ContractPickerModelProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region HedgePricingItemsSource
|
||||
public static readonly DependencyProperty HedgePricingItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
|
||||
nameof(HedgePricingItemsSource));
|
||||
|
||||
public IEnumerable HedgePricingItemsSource {
|
||||
get => GetValue(HedgePricingItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(HedgePricingItemsSourceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HedgePricing
|
||||
public static readonly DependencyProperty HedgePricingProperty =
|
||||
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
|
||||
nameof(HedgePricing),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public object HedgePricing {
|
||||
get => GetValue(HedgePricingProperty);
|
||||
set => SetValue(HedgePricingProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate() =>
|
||||
_connect_leg_tuples();
|
||||
|
||||
@@ -251,7 +275,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactors_Picker");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -32,6 +33,7 @@ namespace mbts.spark {
|
||||
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
|
||||
TickFactors _tick_factors;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
|
||||
/// <summary>
|
||||
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
|
||||
@@ -294,6 +296,9 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
// hedge pricing
|
||||
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
|
||||
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -301,6 +306,7 @@ namespace mbts.spark {
|
||||
foreach (var lt in leg_tuples) {
|
||||
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
|
||||
}
|
||||
mutable_strategy.HedgePricing = hedge_pricing;
|
||||
return new ExecutionStrategyImmutable(mutable_strategy);
|
||||
}
|
||||
|
||||
@@ -308,6 +314,7 @@ namespace mbts.spark {
|
||||
_synthetic = synthetic.CloneToMutable();
|
||||
_synthetic.Exchange = Venue.Bts;
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
RaisePropertyChanged(nameof(name), nameof(tick_factors));
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using mbts.collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class's primary purpose is to handle async AlgoService persistence calls for the AlgoStudioVM/OrderPropertiesEditorVM.
|
||||
/// When a new definition/template item is persisted for the first time, it's corresponding viewmodel (<see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>)
|
||||
/// must be removed and replaced with a new one that has the brand new persisted id (<see cref="AlgoKey"/>). As part of its duty, this class makes sure that the new persisted items are inserted into the same index as their unpersisted predecessor.
|
||||
/// </summary>
|
||||
static class AlgoPersistenceHelper {
|
||||
public static (int? def_index, int? tmp_index) selection_snapshot(IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM selected_definition, AlgoDefinitionTemplateVM selected_template) {
|
||||
var def = selected_definition;
|
||||
var tmp = selected_template;
|
||||
int? def_index = def == null ? (int?)null : definitions.IndexOf(def);
|
||||
int? tmp_index = tmp == null ? (int?)null : tmp.owner.templates.IndexOf(tmp);
|
||||
return (def_index, tmp_index);
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_definition(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM definition) {
|
||||
int def_index = definitions.IndexOf(definition);
|
||||
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, definition.templates.AsList()), t => t.def));
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_template(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionTemplateVM template) {
|
||||
switch (template.owner.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return await persist_definition(model, definitions, template.owner);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
int def_index = definitions.IndexOf(template.owner);
|
||||
int tmp_index = template.owner.templates.IndexOf(template);
|
||||
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, def.templates.WrapSubRange(tmp_index, 1)), t => t.def));
|
||||
default: return DictionaryHelper.empty<AlgoKey, AlgoKey>();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_all(OrdersModel model, ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> definitions) =>
|
||||
await persist(model, definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
|
||||
|
||||
/// <summary>
|
||||
/// Persists each pair of definition/templates, then returns a map of non-persisted keys to persisted ones (<see cref="AlgoKey"/>, <see cref="AlgoDefinitionVM.key"/>, <see cref="AlgoDefinitionTemplateVM.key"/>).
|
||||
/// The key-map gives callers the ability to replace unpersisted items with persisted ones before the next update arrives.
|
||||
/// <para>Note: use a <see cref="mbts.utils.TableUpdateBuffer{T, TKey}"/> to suspend updates while awaiting this task; don't resume them until all unpersisted items have been replaced.
|
||||
/// <see cref="AlgoDefinitionCollection"/> caters to this pre-emptive adding behavior, in it's <see cref="TableUpdate{T, TKey}"/> callback, it checks if added items are already present, and if so, treats them as changes instead.</para>
|
||||
/// <para>PS: the reason for all this madness is so newly persisted items are inserted in the same index as their placeholder counterpart. The only way to connect the non-persisted keys with their persisted counterparts
|
||||
/// is to await each save-op individually, and build a map with the results..hence the returned key-map.</para>
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="definition_templates"></param>
|
||||
/// <returns>An AlgoKey->AlgoKey map of old placeholder ids to newly persisted ones.</returns>
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist(OrdersModel model, IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
|
||||
var placeholder_to_persisted_keys = new Dictionary<AlgoKey, AlgoKey>();
|
||||
for (int i = 0; i < definition_templates.Count; i++) {
|
||||
(var def, var templates) = definition_templates[i];
|
||||
var persisted_def = def;
|
||||
if (def.is_dirty) {
|
||||
var def_record = await model.algo_model.save_definition(def.create_algo_def());
|
||||
if (def_record.HasValue) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
persisted_def = def.to_persisted(def_record.Value);
|
||||
foreach (var tmp in templates) {
|
||||
AlgoDefinitionTemplateVM persisted_tmp;
|
||||
var tmp_record = await model.algo_model.save_template(tmp.create(def_record.Value));
|
||||
if (tmp_record.HasValue) {
|
||||
persisted_def.templates.Add(persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key)));
|
||||
} else {
|
||||
/// If the save-op fails, the unpersisted template is cloned and added to the persisted def's template items, so the user may try to save it again without having to re-enter all the templated-values.
|
||||
/// Q: "why don't we just re-add the same template to the persisted def, the save-op failed, so nothing changed...why do we need a new one?"
|
||||
/// A: "A new template vm must be created because its owner has changed (it's a readonly prop, <see cref="AlgoDefinitionTemplateVM.owner"/>)."
|
||||
persisted_def.templates.Add(persisted_tmp = new AlgoDefinitionTemplateVM(persisted_def, model, tmp.create_def_data()));
|
||||
}
|
||||
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
|
||||
}
|
||||
persisted_def.connect();
|
||||
if (def.key.mode == AlgoKey.Mode.Unpersisted) {
|
||||
definition_templates[i] = (persisted_def, persisted_def.templates);
|
||||
} else {
|
||||
definition_templates.Add((persisted_def, persisted_def.templates));
|
||||
}
|
||||
placeholder_to_persisted_keys[def.key] = persisted_def.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// so the def isn't dirty, but it still might have dirty templates
|
||||
using (persisted_def.buffer_updates()) {
|
||||
for (int j = 0; j < templates.Count; j++) {
|
||||
var tmp = templates[j];
|
||||
if (!tmp.is_dirty)
|
||||
continue;
|
||||
var tmp_record = await model.algo_model.save_template(tmp.create());
|
||||
if (tmp_record.HasValue) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
var persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key));
|
||||
templates[j] = persisted_tmp;
|
||||
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return placeholder_to_persisted_keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,11 @@
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:StateManager.VisualState="{Binding content_source.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
|
||||
u:StateManager.VisualState="{Binding selected_definition.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
@@ -33,9 +30,6 @@
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<cv:ValueMap x:Key="AlgoTemplateFavoriteToggleButtonToolTipConverter" Default="Add to Favorites">
|
||||
<cv:KeyValue Key="{u:True}" Value="Remove from Favorites"/>
|
||||
</cv:ValueMap>
|
||||
<sys:String x:Key="AlgoAbbreviationActiveState">AbbreviationActive</sys:String>
|
||||
<sys:String x:Key="AlgoAbbreviationInactiveState">AbbreviationInactive</sys:String>
|
||||
<cv:ValueMap x:Key="AlgoAbbreviationVisualStateConverter" Default="{StaticResource AlgoAbbreviationActiveState}">
|
||||
@@ -130,70 +124,8 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoTemplateFavoriteToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="LightGray"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="IsThreeState" Value="False"/>
|
||||
<Setter Property="IsChecked" Value="{Binding is_favorite}"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="ToolTip" Value="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static uag:AlgoTemplateToolBar.FavoriteToggleButtonToolTipConverter}}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
|
||||
<Ellipse x:Name="ellipse" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}" Width="0" Height="0"/>
|
||||
<Image SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}" MaxHeight="13" MaxWidth="13">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Geometry="{Binding Source={StaticResource ResourceKey={x:Static u:Icons.StarGeometryKey}}}" Brush="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" >
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen
|
||||
Brush="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Thickness="{Binding BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Foreground" Value="Gold"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="16" Duration="0:0:0.03" />
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="16" Duration="0:0:0.03" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="0" Duration="0:0:0.0" />
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.0" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext.content_source, RelativeSource={RelativeSource AncestorType=usp:AlgoStudioView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
<Setter Property="DropDownStyleKey" Value="{x:Static usp:AlgoContentTemplateSelector.DefinitionDropDownStyleKey}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
@@ -221,94 +153,26 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TreeView
|
||||
<ListBox
|
||||
x:Name="_tree_view"
|
||||
u:TreeViewEx.SelectedItemChangedHandler="{Binding}"
|
||||
ItemsSource="{Binding definitions_view}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
MinWidth="180"
|
||||
Background="Transparent"
|
||||
Style="{StaticResource AlgoTemplateTreeViewStyle}"
|
||||
SelectedItem="{Binding selected_definition}"
|
||||
>
|
||||
<TreeView.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.AddNewItemCommand}" Executed="{x:Static u:EditableCollectionViewEx.AddNewItemExecuted}" CanExecute="{x:Static u:EditableCollectionViewEx.AddNewItemCanExecute}"/>
|
||||
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.CommitAnyCommand}" Executed="{x:Static u:EditableCollectionViewEx.CommitAnyExecuted}"/>
|
||||
</TreeView.CommandBindings>
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type sp:AlgoDefinitionVM}" ItemsSource="{Binding templates_view}">
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="badge" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="badge"
|
||||
Fill="{Binding badge_fill}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
Visibility="{DynamicResource ResourceKey={x:Static usp:AlgoStudioView.BadgesInTreeViewVisibilityKey}}"
|
||||
/>
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
|
||||
</Grid>
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}" Margin="-6,0,0,0">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="favorite_toggle" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton x:Name="favorite_toggle" Grid.Column="1" Margin="4,0,0,0" Style="{StaticResource AlgoTemplateFavoriteToggleButtonStyle}"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}" ToolTip="Double click to set as default"/>
|
||||
</Grid>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{StaticResource OrConverter}">
|
||||
<Binding Path="is_dirty"/>
|
||||
<Binding Path="is_persisted" Converter="{StaticResource NotConverter}"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</TreeView.Resources>
|
||||
</TreeView>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
|
||||
<Grid x:Name="edit_pane">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -430,7 +294,7 @@
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding content_source.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
/>
|
||||
<Grid x:Name="abbreviation_root" Grid.Column="2">
|
||||
<Grid.ToolTip>
|
||||
@@ -452,7 +316,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding content_source.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
|
||||
@@ -478,8 +342,8 @@
|
||||
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
|
||||
<usp:OrderTypeItem
|
||||
Margin="2,0"
|
||||
BadgeFill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding content_source.algo_root.display_name}"
|
||||
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding selected_definition.algo_root.display_name}"
|
||||
/>
|
||||
<TextBlock>).</TextBlock>
|
||||
</StackPanel>
|
||||
@@ -496,7 +360,7 @@
|
||||
x:Name="default_badge"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Fill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -506,7 +370,7 @@
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="{StaticResource Margin.LP2}"
|
||||
IsChecked="{Binding content_source.custom_badge_color.enabled}"
|
||||
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
|
||||
GroupName="Badge"
|
||||
>
|
||||
<RadioButton.ToolTip>
|
||||
@@ -526,7 +390,7 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="3,0"
|
||||
Fill="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
|
||||
@@ -541,7 +405,7 @@
|
||||
<Button
|
||||
x:Name="custom_badge_color_picker_button"
|
||||
Grid.Column="1"
|
||||
Background="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
||||
/>
|
||||
@@ -549,8 +413,8 @@
|
||||
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
||||
<cp:ColorPickerControl
|
||||
Background="Transparent"
|
||||
Palettes="{Binding DataContext.content_source.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.content_source.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
/>
|
||||
</Border>
|
||||
</Popup>
|
||||
@@ -559,7 +423,7 @@
|
||||
</Grid>
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding content_source}"
|
||||
DataContext="{Binding selected_definition}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
@@ -569,10 +433,10 @@
|
||||
<StackPanel.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
|
||||
</StackPanel.Margin>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@@ -580,4 +444,4 @@
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -1,43 +1,24 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using System;
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoStudioView : UserControl {
|
||||
public const string BadgesInTreeViewVisibilityKey = nameof(BadgesInTreeViewVisibilityKey);
|
||||
public static readonly IValueConverter EditPaneVisualStateConverter =
|
||||
ValueConverter.create(obj => obj == null ? VisualStates.Inactive : VisualStates.Active);
|
||||
|
||||
IAlgoStudioViewController _controller;
|
||||
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
set_controller();
|
||||
// local methods
|
||||
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
|
||||
PopupViewController.attach(custom_badge_color_picker_popup, custom_badge_color_picker_button);
|
||||
this.ConnectDataContext<AlgoStudioVM>(vm => vm.request_view_focus_name += focus_name, vm => vm.request_view_focus_name -= focus_name);
|
||||
void focus_name(AlgoDefinitionVM target) =>
|
||||
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
|
||||
}
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
}
|
||||
|
||||
public void add_definition() => _controller.add_definition();
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) => _controller.add_definition_clone(src_definition);
|
||||
public void add_template(AlgoDefinitionVM definition) => _controller.add_template(definition);
|
||||
public void rename_definition(AlgoDefinitionVM definition) => _controller.rename_definition(definition);
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) => _controller.rename_template(template);
|
||||
public void select_definition(AlgoDefinitionVM definition) => _controller.select_definition(definition);
|
||||
public void select_template(AlgoDefinitionTemplateVM template) => _controller.select_template(template);
|
||||
public IDisposable preserve_selected_definition() => _controller.preserve_selected_definition();
|
||||
|
||||
void _end_edit_executed(object s, ExecutedRoutedEventArgs e) =>
|
||||
_controller.end_edit(e);
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class EditPane {
|
||||
public const string GroupName = nameof(EditPane);
|
||||
@@ -75,14 +56,6 @@ namespace mbts.ui.spark {
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class TreeViewBadge {
|
||||
public const string GroupName = nameof(TreeViewBadge);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class FavoriteToggleButton {
|
||||
public const string GroupName = nameof(FavoriteToggleButton);
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
interface IAlgoStudioViewController : IDisposable {
|
||||
void add_definition();
|
||||
void add_definition_clone(AlgoDefinitionVM src_definition);
|
||||
void add_template(AlgoDefinitionVM definition);
|
||||
void rename_definition(AlgoDefinitionVM definition);
|
||||
void rename_template(AlgoDefinitionTemplateVM template);
|
||||
void end_edit(ExecutedRoutedEventArgs e);
|
||||
void select_definition(AlgoDefinitionVM definition);
|
||||
void select_template(AlgoDefinitionTemplateVM template);
|
||||
IDisposable preserve_selected_definition();
|
||||
}
|
||||
|
||||
class AlgoStudioViewControllerFactory {
|
||||
static readonly IAlgoStudioViewController _empty = new EmptyController();
|
||||
|
||||
public static IAlgoStudioViewController create(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) =>
|
||||
vm == null || tree_view == null || name_text_box == null || color_picker_button == null || color_picker_popup == null ?
|
||||
_empty : new Controller(vm, tree_view, name_text_box, color_picker_button, color_picker_popup);
|
||||
|
||||
/// <summary>
|
||||
/// This wrapper class is returned by <see cref="create(AlgoStudioVM, TreeView)"/> when the vm or tree_view is null.
|
||||
/// This further separates the view from the controller-logic; it doesn't even need to check for null when accessing <see cref="IAlgoStudioViewController"/> methods.
|
||||
/// </summary>
|
||||
class EmptyController : IAlgoStudioViewController {
|
||||
public void add_definition() { }
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) { }
|
||||
public void add_template(AlgoDefinitionVM definition) { }
|
||||
public void rename_definition(AlgoDefinitionVM definition) { }
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) { }
|
||||
public void end_edit(ExecutedRoutedEventArgs e) { }
|
||||
public void select_definition(AlgoDefinitionVM definition) { }
|
||||
public void select_template(AlgoDefinitionTemplateVM template) { }
|
||||
public IDisposable preserve_selected_definition() => Disposable.empty;
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
class Controller : IAlgoStudioViewController {
|
||||
readonly AlgoStudioVM _vm;
|
||||
readonly TreeView _tree_view;
|
||||
readonly TextBox _name_text_box;
|
||||
IDisposable _dispose;
|
||||
|
||||
public Controller(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) {
|
||||
_vm = vm;
|
||||
_tree_view = tree_view;
|
||||
_name_text_box = name_text_box;
|
||||
_dispose = PopupViewController.attach(color_picker_popup, color_picker_button);
|
||||
}
|
||||
|
||||
public void add_definition() {
|
||||
_vm.add_new_definition();
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) {
|
||||
_vm.add_new_definition_clone(src_definition);
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void add_template(AlgoDefinitionVM def) {
|
||||
var new_tmp = def.template_factory();
|
||||
def.templates.Add(new_tmp);
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void end_edit(ExecutedRoutedEventArgs e) {
|
||||
if (e.OriginalSource is EditableTextBlock etb) {
|
||||
etb.end_edit();
|
||||
_get_container(etb.DataContext)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void rename_definition(AlgoDefinitionVM definition) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(definition) is TreeViewItem tvi) {
|
||||
var etb = tvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
|
||||
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem tvi) {
|
||||
if (tvi.ItemContainerGenerator.ContainerFromItem(template) is TreeViewItem ttvi) {
|
||||
var etb = ttvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
|
||||
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void select_definition(AlgoDefinitionVM definition) =>
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(definition)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
|
||||
public void select_template(AlgoDefinitionTemplateVM template) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem def_view) {
|
||||
def_view.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
def_view.ItemContainerGenerator.ContainerFromItem(template)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable preserve_selected_definition() =>
|
||||
Disposable.Create(() => {
|
||||
var name = _vm.selected_definition.name;
|
||||
if (_vm.definitions.TryGetFirst(d => d.name == name, out AlgoDefinitionVM def))
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(def)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
});
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
void _begin_edit() =>
|
||||
WPFHelper.push_frame_until(() => Keyboard.Focus(_name_text_box) == _name_text_box);
|
||||
|
||||
TreeViewItem _get_container(object obj) =>
|
||||
obj is AlgoDefinitionTemplateVM t ? _get_container(t) :
|
||||
obj is AlgoDefinitionVM d ? _get_container(d) : null;
|
||||
|
||||
TreeViewItem _get_container(AlgoDefinitionVM d) =>
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(d) as TreeViewItem;
|
||||
|
||||
TreeViewItem _get_container(AlgoDefinitionTemplateVM t) =>
|
||||
_get_container(t.owner) is TreeViewItem dview ? dview.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,35 +62,19 @@ namespace mbts.spark {
|
||||
_vm = new AlgoStudioVM(_model);
|
||||
_vm.load_from(_state);
|
||||
_view = new AlgoStudioView { DataContext = _vm };
|
||||
_view.Resources[AlgoStudioView.BadgesInTreeViewVisibilityKey] = _ui_configuration.show_badges_in_tree_view ? Visibility.Visible : Visibility.Collapsed;
|
||||
_resources = new CompositeDisposable();
|
||||
// observable sources
|
||||
var content_src = _vm.observe_selected().Select(_ => _vm.content_source);
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, new DelegateCommand(_view.add_definition)));
|
||||
// add definition clone
|
||||
var add_definition_clone_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.DocumentCopy, d => $"Clone '{d?.name}'", d => d != null, _view.add_definition_clone);
|
||||
_commands.Add(add_definition_clone_spark_cmd);
|
||||
// add template
|
||||
var add_template_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.AddFile, d => $"Add '{d?.name}' Template", d => d != null, _view.add_template) {
|
||||
show_in_toolbar = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar) == bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar,
|
||||
show_in_context_menu = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu) == bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu
|
||||
};
|
||||
_commands.Add(add_template_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(add_definition_clone_spark_cmd, add_template_spark_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
|
||||
_commands.Add(clone_selected_definition_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
|
||||
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
|
||||
// save
|
||||
if (_ui_configuration.can_save_specific) {
|
||||
var content_is_dirty_src = _vm.observe_selected().SelectManyInSeries(t => {
|
||||
if (t.template != null) {
|
||||
return t.template.observe_is_dirty();
|
||||
} else if (t.definition != null) {
|
||||
return t.definition.subscribe_is_definition_dirty();
|
||||
} else return Observable.Return(false);
|
||||
});
|
||||
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
|
||||
_vm.save_template(t).ignore();
|
||||
else if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
|
||||
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
_vm.save_definition(d).ignore();
|
||||
}, content_is_dirty_src));
|
||||
}
|
||||
@@ -101,48 +85,39 @@ namespace mbts.spark {
|
||||
// undo/redo
|
||||
if (_ui_configuration.can_undo_redo) {
|
||||
// undo
|
||||
var undo_cmd = new ObservantCommand(() => _vm.content_source?.try_undo());
|
||||
var undo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_undo());
|
||||
var undo_spark_cmd = new SparkCommandCommand("Undo", Icons.UndoCircular, undo_cmd);
|
||||
var can_undo_source = _vm.observe_selected().SelectManyInSeries(tup => {
|
||||
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
|
||||
return content != null ?
|
||||
var can_undo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
|
||||
def != null ?
|
||||
Observable.Create<bool>(o => {
|
||||
content.can_undo_changed += notify;
|
||||
def.can_undo_changed += notify;
|
||||
notify();
|
||||
return Disposable.Create(() => content.can_undo_changed -= notify);
|
||||
void notify() => o.OnNext(content.can_undo);
|
||||
return Disposable.Create(() => def.can_undo_changed -= notify);
|
||||
void notify() => o.OnNext(def.can_undo);
|
||||
}) :
|
||||
Observable.Return(false);
|
||||
});
|
||||
Observable.Return(false));
|
||||
_commands.Add(undo_spark_cmd);
|
||||
_resources.Add(can_undo_source.Subscribe(undo_cmd));
|
||||
// redo
|
||||
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
|
||||
var redo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_redo());
|
||||
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
|
||||
var can_redo_source = _vm.observe_selected().SelectManyInSeries(tup => {
|
||||
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
|
||||
return content != null ?
|
||||
var can_redo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
|
||||
def != null ?
|
||||
Observable.Create<bool>(o => {
|
||||
content.can_redo_changed += notify;
|
||||
def.can_redo_changed += notify;
|
||||
notify();
|
||||
return Disposable.Create(() => content.can_redo_changed -= notify);
|
||||
void notify() => o.OnNext(content.can_redo);
|
||||
return Disposable.Create(() => def.can_redo_changed -= notify);
|
||||
void notify() => o.OnNext(def.can_redo);
|
||||
}) :
|
||||
Observable.Return(false);
|
||||
});
|
||||
Observable.Return(false));
|
||||
_commands.Add(redo_spark_cmd);
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
if (value is AlgoDefinitionTemplateVM t) {
|
||||
_vm.remove_template(t).ignore();
|
||||
} else if (value is AlgoDefinitionVM d) {
|
||||
if (value is AlgoDefinitionVM d) {
|
||||
string prompt; MessageBoxImage icon;
|
||||
if (_model.algo_model.templates_enabled() && d.templates.Count > 0) {
|
||||
prompt = $"Deleting this definition ({d.name}) will also delete its templates ({d.templates.Count}).\n\nDo you wan to Continue?";
|
||||
icon = MessageBoxImage.Warning;
|
||||
} else if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
prompt = $"Are you sure you want to delete '{d.name}?'";
|
||||
icon = MessageBoxImage.Question;
|
||||
} else {
|
||||
@@ -162,7 +137,7 @@ namespace mbts.spark {
|
||||
}
|
||||
});
|
||||
_commands.Add(remove_spark_cmd);
|
||||
_resources.Add(content_src.OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
}
|
||||
|
||||
void _dispose_core() {
|
||||
|
||||
@@ -1,31 +1,57 @@
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
struct AlgoData : IEquatable<AlgoData> {
|
||||
public readonly CustomOrderDefinitionImmutable value;
|
||||
public readonly string name;
|
||||
public readonly AlgoKey key;
|
||||
|
||||
public AlgoData(CustomOrderDefinitionImmutable value)
|
||||
: this(value, value.display_name(), OrderDataHelper.get_shallow_factory_key(value)) { }
|
||||
|
||||
public AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
|
||||
AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public AlgoData with(CustomOrderDefinitionImmutable? value = null, string name = null) =>
|
||||
new AlgoData(value ?? this.value, name ?? this.name, key);
|
||||
/// <summary>
|
||||
/// This is the only <see cref="AlgoData"/> factory method that allows callers to explicitly specify a name.
|
||||
/// It should only be used within the context of creating, or renaming, studio algos.
|
||||
/// </summary>
|
||||
public static AlgoData create(CustomOrderDefinitionImmutable def, string name, AlgoKey key) =>
|
||||
new AlgoData(def, name, key);
|
||||
|
||||
/// <summary>
|
||||
/// Reconstructs <see cref="AlgoData"/> from a <see cref="CustomOrderDefinitionImmutable"/>, with a special caveat in the case of <see cref="CompositeOrderDefinition"/>:
|
||||
/// a fallback/default name is used instead of the composite algo's true name, which must be obtained from the <see cref="IAlgoStudioModel.definitions"/> stream.
|
||||
/// </summary>
|
||||
public static AlgoData from_custom_def(CustomOrderDefinitionImmutable def) {
|
||||
switch (def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite:
|
||||
string name = $"({OrderDataHelper.abbreviate_algo_type(def.CustomCase)}){OrderDataHelper.abbreviate_algo_type(def.Composite.Definition.CustomCase)}";
|
||||
return new AlgoData(def.Composite.Definition, name, AlgoKey.from_composite_key(def.Composite.AlgoTemplateKey));
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
return new AlgoData(def,def.Dynamic.DynamicOrderType, AlgoKey.from_dynamic_type(def.Dynamic.DynamicOrderType));
|
||||
default:
|
||||
return new AlgoData(def, def.CustomCase.display_name(), AlgoKey.from_known_type(def.CustomCase));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData from_record(AlgoDefinitionRecordImmutable record) =>
|
||||
new AlgoData(record.Algo.Definition, record.Algo.Name, AlgoKey.from_composite_key(record.Key));
|
||||
|
||||
public static AlgoData create_unpersisted(CustomOrderDefinitionImmutable custom_def, string name) =>
|
||||
new AlgoData(custom_def, name, AlgoKey.create_unpersisted());
|
||||
|
||||
public bool Equals(AlgoData other) =>
|
||||
key == other.key &&
|
||||
name == other.name &&
|
||||
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.Equals(value, other.value);
|
||||
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer.Equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() =>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
[ProtoContract]
|
||||
struct AlgoID : IEquatable<AlgoID> {
|
||||
[ProtoMember(1)]
|
||||
@@ -47,24 +48,23 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
return AlgoStructureSerializer.to_algo_id(key.factory.structure, dynamic_type_index);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
case AlgoKey.Mode.Factory: return new AlgoID(key.factory);
|
||||
case AlgoKey.Mode.Dynamic: return new AlgoID(key.dynamic_type);
|
||||
case AlgoKey.Mode.Persisted: return new AlgoID(key.persisted);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
public static AlgoKey to_algo_key(AlgoID id, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
public static AlgoKey to_algo_key(AlgoID id) {
|
||||
if (id._factory_key.HasValue)
|
||||
return new AlgoKey(id._factory_key.Value);
|
||||
return AlgoKey.from_known_type(id._factory_key.Value);
|
||||
if (id._persisted_key.HasValue)
|
||||
return new AlgoKey(new AlgoPersistedKey(id._persisted_key.Value));
|
||||
return AlgoKey.from_composite_key(id._persisted_key.Value);
|
||||
if (id._dynamic_key != null)
|
||||
return new AlgoKey(new AlgoNameStructureKey(id._dynamic_key, AlgoStructureSerializer.structure_key(dynamic_type_index.to_int(id._dynamic_key))));
|
||||
return default;
|
||||
return AlgoKey.from_dynamic_type(id._dynamic_key);
|
||||
throw new ArgumentException("ID is empty.", nameof(id));
|
||||
}
|
||||
|
||||
public bool Equals(AlgoID other) =>
|
||||
@@ -85,55 +85,93 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
|
||||
struct AlgoKey : IEquatable<AlgoKey> {
|
||||
readonly AlgoPersistedKey? _persisted_key;
|
||||
readonly AlgoNameStructureKey? _factory_key;
|
||||
readonly uint? _composite_key;
|
||||
readonly Guid? _unpersisted;
|
||||
readonly string _dynamic_type;
|
||||
readonly CustomOrderDefinition.CustomOneofCase? _factory_type;
|
||||
|
||||
public AlgoKey(Guid id_guid) {
|
||||
_persisted_key = null;
|
||||
_factory_key = null;
|
||||
AlgoKey(Guid id_guid) {
|
||||
_composite_key = null;
|
||||
_unpersisted = id_guid;
|
||||
_dynamic_type = null;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_root_algo_type)
|
||||
: this(new AlgoNameStructureKey(factory_root_algo_type)) { }
|
||||
|
||||
public AlgoKey(AlgoNameStructureKey factory_key) {
|
||||
_persisted_key = null;
|
||||
_factory_key = factory_key;
|
||||
public AlgoKey(uint composite_key) {
|
||||
_composite_key = composite_key;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = null;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(AlgoPersistedKey persisted_key) {
|
||||
_persisted_key = persisted_key;
|
||||
_factory_key = null;
|
||||
public AlgoKey(string dynamic_type) {
|
||||
_composite_key = null;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = dynamic_type;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_type) {
|
||||
_composite_key = null;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = null;
|
||||
_factory_type = factory_type;
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
None,
|
||||
Persisted,
|
||||
Unpersisted,
|
||||
Dynamic,
|
||||
Factory
|
||||
}
|
||||
|
||||
public bool is_persisted => mode == Mode.Persisted;
|
||||
|
||||
public AlgoPersistedKey persisted => _persisted_key.Value;
|
||||
public AlgoNameStructureKey factory => _factory_key ?? new AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase.None);
|
||||
public uint persisted => _composite_key.Value;
|
||||
public Guid unpersisted => _unpersisted.Value;
|
||||
public CustomOrderDefinition.CustomOneofCase factory => _factory_type.Value;
|
||||
public string dynamic_type => _dynamic_type;
|
||||
|
||||
public Mode mode {
|
||||
get {
|
||||
if (_persisted_key.HasValue) return Mode.Persisted;
|
||||
if (_factory_type.HasValue) return Mode.Factory;
|
||||
if (_composite_key.HasValue) return Mode.Persisted;
|
||||
if (_dynamic_type != null) return Mode.Dynamic;
|
||||
if (_unpersisted.HasValue) return Mode.Unpersisted;
|
||||
if (_factory_key.HasValue) return Mode.Factory;
|
||||
return Mode.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoKey create_unpersisted() =>
|
||||
new AlgoKey(Guid.NewGuid());
|
||||
|
||||
public static AlgoKey from_custom_def(CustomOrderDefinitionImmutable custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoKey(custom_def.Dynamic.DynamicOrderType);
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoKey(custom_def.Composite.AlgoTemplateKey);
|
||||
default: return new AlgoKey(custom_def.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoKey from_known_type(CustomOrderDefinition.CustomOneofCase known_type) {
|
||||
if (known_type == CustomOrderDefinition.CustomOneofCase.Dynamic)
|
||||
throw new ArgumentException(nameof(known_type), "Potential loss of data: no dynamic type name specified.");
|
||||
if (known_type == CustomOrderDefinition.CustomOneofCase.Composite)
|
||||
throw new ArgumentException(nameof(known_type), "Potential loss of data: no composite id specified.");
|
||||
return new AlgoKey(known_type);
|
||||
}
|
||||
|
||||
public static AlgoKey from_dynamic_type(string dynamic_type) {
|
||||
if (string.IsNullOrEmpty(dynamic_type))
|
||||
throw ExceptionBuilder.Argument.null_or_empty(nameof(dynamic_type));
|
||||
return new AlgoKey(dynamic_type);
|
||||
}
|
||||
|
||||
public static AlgoKey from_composite_key(uint composite_key) =>
|
||||
new AlgoKey(composite_key);
|
||||
|
||||
public bool Equals(AlgoKey other) {
|
||||
Mode m = mode;
|
||||
if (m == other.mode) {
|
||||
@@ -141,6 +179,7 @@ namespace mbts.spark {
|
||||
case Mode.Persisted: return persisted == other.persisted;
|
||||
case Mode.Unpersisted: return unpersisted == other.unpersisted;
|
||||
case Mode.Factory: return factory == other.factory;
|
||||
case Mode.Dynamic: return dynamic_type == other.dynamic_type;
|
||||
case Mode.None: return true;
|
||||
}
|
||||
}
|
||||
@@ -155,6 +194,7 @@ namespace mbts.spark {
|
||||
case Mode.Persisted: hash = persisted.GetHashCode(); phase_mult = 2; break;
|
||||
case Mode.Unpersisted: hash = unpersisted.GetHashCode(); phase_mult = 4; break;
|
||||
case Mode.Factory: hash = factory.GetHashCode(); phase_mult = 3; break;
|
||||
case Mode.Dynamic: hash = dynamic_type.GetHashCode(); phase_mult = 5; break;
|
||||
default: return 0;
|
||||
}
|
||||
return EqualityComparer.phase_hash(hash, phase_mult);
|
||||
@@ -165,4 +205,4 @@ namespace mbts.spark {
|
||||
|
||||
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace bts.persist.algo {
|
||||
public bool is_persisted => mode != Mode.None;
|
||||
|
||||
public mbts.spark.AlgoKey to_algo_key() =>
|
||||
new mbts.spark.AlgoKey(this);
|
||||
new mbts.spark.AlgoKey(_id_uint ?? 0);
|
||||
|
||||
public bool Equals(AlgoPersistedKey other) {
|
||||
Mode m = mode;
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
struct AlgoNameStructureKey : IEquatable<AlgoNameStructureKey> {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, AlgoStructureKey structure) {
|
||||
this.name = name;
|
||||
this.structure = structure;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoNameStructureKey other) => name == other.name && structure == other.structure;
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(EqualityComparer.safe_get_hash_code(name), structure.GetHashCode());
|
||||
public static bool operator ==(AlgoNameStructureKey a, AlgoNameStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) {
|
||||
if (_structure == null) {
|
||||
return other._structure == null;
|
||||
} else {
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
return other._structure != null && _structure.Length == other._structure.Length && _structure.SequenceEqual(other._structure);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoStructureKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_structure == null ? 0 : EqualityComparer.aggregate_hash(_structure);
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
|
||||
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static AlgoStructureKey structure_key(int dynamic_type) =>
|
||||
new AlgoStructureKey(new[] { dynamic_type | _dynamic_mask });
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
public static AlgoID to_algo_id(AlgoStructureKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (key.structure.Count == 0)
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
int i = key.structure[0];
|
||||
return BitHelper.remove_flags(ref i, _dynamic_mask) ? new AlgoID(dynamic_type_index.from_int(i)) : new AlgoID((CustomOrderDefinition.CustomOneofCase)i);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-319
@@ -1,319 +0,0 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoStudioDefinitionTemplateModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
readonly ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata> _metadata_demuxer = new ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata>();
|
||||
readonly TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey> _definition_templates_demuxer;
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
public AlgoStudioDefinitionTemplateModel(AlgoService service, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
|
||||
_service = service;
|
||||
metadata_service = meta;
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
_definition_templates_demuxer = new TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey>(t => t.key, t => t.definition_key);
|
||||
definitions = _service.subscribei_algos()
|
||||
.Select(u => new Update<AlgoData, AlgoKey>(
|
||||
u.all.Select(_Utils.definition_data).ToList(),
|
||||
u.added.Select(_Utils.definition_data).ToList(),
|
||||
u.changes.Select(ch => new Change<AlgoData>(_Utils.definition_data(ch.Old), _Utils.definition_data(ch.New))).ToList(),
|
||||
u.removed.Select(d => _Utils.definition_data(d)).ToList(),
|
||||
def => def.key))
|
||||
.Table()
|
||||
.Cache();
|
||||
templates = _service.subscribei_algo_templates()
|
||||
.Select(u => new Update<AlgoTemplateData, AlgoKey>(
|
||||
u.all.Select(_Utils.template_data).ToList(),
|
||||
u.added.Select(_Utils.template_data).ToList(),
|
||||
u.changes.Select(ch => new Change<AlgoTemplateData>(_Utils.template_data(ch.Old), _Utils.template_data(ch.New))).ToList(),
|
||||
u.removed.Select(_Utils.template_data).ToList(),
|
||||
def => def.key))
|
||||
.Table()
|
||||
.Cache();
|
||||
_dispose = new CompositeDisposable(
|
||||
definitions.OnDispatcher().Subscribe(t => _algos = t.state),
|
||||
templates.OnDispatcher().Subscribe(t => _algo_templates = t.state),
|
||||
templates.Subscribe(_definition_templates_demuxer),
|
||||
metadata_service.subscribe_metadata().Subscribe(_metadata_demuxer));
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition) =>
|
||||
_definition_templates_demuxer.subscribe(definition);
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
if (definition_data.key.is_persisted) {
|
||||
// TODO: implement update op when backend ready
|
||||
} else {
|
||||
// create op
|
||||
return await _create_definition(new CompositeAlgoDefinitionImmutable(_Utils.to_definition(definition_data)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public async Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) {
|
||||
if (template_data.key.is_persisted) {
|
||||
// update op
|
||||
return await _update_template(_Utils.to_recordi(template_data));
|
||||
} else {
|
||||
// create op
|
||||
return await _create_template(_Utils.to_templatei(template_data));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The api is not used here (there are some unresolved dependency issues with definitions on the backend).
|
||||
/// The definition is marked as hidden in the AlgoMetadata stored on the server.
|
||||
/// <para/>
|
||||
/// For implementation details, see: <see cref="IAlgoDefinitionMetadataService"/>, <see cref="AlgoTemplateMetadataService"/>
|
||||
/// </summary>
|
||||
public async Task<bool> remove_definition(AlgoKey key) {
|
||||
if (_algos.TryGetValue(key, out AlgoData curr)) {
|
||||
if (_definition_templates_demuxer.try_get_values(key, out IReadOnlyDictionary<AlgoKey, AlgoTemplateData> tmps)) {
|
||||
foreach (var t in tmps.Values)
|
||||
await remove_template(t.key);
|
||||
}
|
||||
await metadata_service.hide_definition(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> remove_template(AlgoKey template_key) {
|
||||
var req = new AlgoServiceRemoveRequest();
|
||||
_Utils.set_key(ref req, template_key);
|
||||
var reply = await _safe_execute(() => _service.remove_template_async(req), ActionMetadata.remove_template);
|
||||
return reply.success && reply.value.Success;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
|
||||
await _create_definition(definition, _admin_model.user.id);
|
||||
|
||||
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition, uint user_id) {
|
||||
(var result, var success) = await _error_reporter.safe_execute(() => _service.create_composite_algo_async(
|
||||
new CompositeAlgoCreateRequest {
|
||||
NewAlgo = definition.CloneToMutable(),
|
||||
UserId = user_id
|
||||
}), r => r.Success, r => r.ErrMsg, "create definition");
|
||||
return success ? _Utils.definition_data(new CompositeAlgoRecordImmutable(result.Record)) : (AlgoData?)null;
|
||||
}
|
||||
|
||||
async Task<Result<T>> _safe_execute<T>(Func<Task<Result<T>>> execute, IActionMetadata<T> am) {
|
||||
try {
|
||||
var result = await execute();
|
||||
if (!result.success) {
|
||||
_report_error(am.present_progressive_desc, result.error_message);
|
||||
} else if (!am.get_success(result.value)) {
|
||||
_report_error(am.present_progressive_desc, am.get_error_msg(result.value));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
_report_error(am.present_progressive_desc, ex.ToString());
|
||||
}
|
||||
return new Result<T>(bts.api.ApiError.UnexpectedResponse);
|
||||
}
|
||||
|
||||
void _report_error(string present_progressive_desc, string msg) =>
|
||||
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
|
||||
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
// used for error handling/reporting
|
||||
interface IActionMetadata {
|
||||
string desc { get; }
|
||||
string present_progressive_desc { get; }
|
||||
}
|
||||
|
||||
interface IActionMetadata<TReply> : IActionMetadata {
|
||||
bool get_success(TReply reply);
|
||||
string get_error_msg(TReply reply);
|
||||
}
|
||||
|
||||
static class ActionMetadata {
|
||||
public const string
|
||||
CreateDefinition = "create definition",
|
||||
UpdateDefinition = "update definition",
|
||||
CreateTemplate = "create template",
|
||||
UpdateTemplate = "update template";
|
||||
public static readonly IActionMetadata<AlgoServiceReply> update_template = new Impl<AlgoServiceReply>(_update, _template, r => r.Success, r => r.ErrMsg.ToString());
|
||||
public static readonly IActionMetadata<AlgoServiceReply> remove_template = new Impl<AlgoServiceReply>(_delete, _template, r => r.Success, r => r.ErrMsg.ToString());
|
||||
public static readonly IActionMetadata<TemplateCreateResult> create_template = new Impl<TemplateCreateResult>(_create, _template, r => r.Success, r => r.ErrMsg);
|
||||
public static readonly IActionMetadata<CompositeAlgoCreateResult> create_definition = new Impl<CompositeAlgoCreateResult>(_create, _definition, r => r.Success, r => r.ErrMsg);
|
||||
|
||||
static readonly DescTuple _create = new DescTuple("create", "creating"), _update = new DescTuple("update", "updating"), _delete = new DescTuple("delete", "deleting");
|
||||
static readonly string _definition = "definition", _template = "template";
|
||||
|
||||
class Impl<T> : IActionMetadata<T> {
|
||||
readonly DescTuple _desc;
|
||||
readonly string _subject;
|
||||
readonly Func<T, bool> _get_success;
|
||||
readonly Func<T, string> _get_error_msg;
|
||||
|
||||
public Impl(DescTuple desc, string subject, Func<T, bool> get_success, Func<T, string> get_error_msg) {
|
||||
_desc = desc;
|
||||
_subject = subject;
|
||||
_get_success = get_success;
|
||||
_get_error_msg = get_error_msg;
|
||||
}
|
||||
public string desc => _desc.desc;
|
||||
public string present_progressive_desc => _desc.present_progressive_desc;
|
||||
public bool get_success(T reply) => _get_success(reply);
|
||||
public string get_error_msg(T reply) => _get_error_msg(reply);
|
||||
}
|
||||
|
||||
struct DescTuple {
|
||||
public readonly string desc, present_progressive_desc;
|
||||
public DescTuple(string desc, string present_progressive_desc) {
|
||||
this.desc = desc;
|
||||
this.present_progressive_desc = present_progressive_desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AlgoService protobuf types have been extracted out of the viewmodels (<see cref="AlgoStudioVM"/>, <see cref="OrderPropertiesEditorVM"/>, <see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>).
|
||||
/// The viewmodels instead use these types to represent the data: <see cref="AlgoData"/> is used in place of <see cref="CompositeAlgoRecordImmutable"/>, and <see cref="AlgoTemplateData"/> is used in place of <see cref="AlgoTemplateRecordImmutable"/>.
|
||||
/// This thin abstraction isolates the server-side from the client-side, so if one day the server side types change, we won't need to change any client-side code except the methods below which handle the conversion from server type to corresponding client type.
|
||||
/// There are a couple other methods in here as well (this is an extensions class afterall), but [John]I am going out of town soon (1/6/18) and did not get a chance to organize these methods beyond containing them in <see cref="Utils"/>.
|
||||
/// </summary>
|
||||
static class _Utils {
|
||||
// AlgoTemplateImmutable custom_order_definition(i)
|
||||
public static CustomOrderDefinitionImmutable custom_order_definitioni(AlgoTemplateImmutable templatei) {
|
||||
switch (templatei.AlgoParamsCase) {
|
||||
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return templatei.CompositeAlgoParams.Algo;
|
||||
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams: return templatei.SystemAlgoParams;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData definition_data(AlgoTemplateImmutable templatei) {
|
||||
switch (templatei.AlgoParamsCase) {
|
||||
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return definition_data(templatei.CompositeAlgoParams);
|
||||
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams:
|
||||
return new AlgoData(
|
||||
templatei.SystemAlgoParams,
|
||||
templatei.SystemAlgoParams.CustomCase.display_name(),
|
||||
get_key(templatei.SystemAlgoParams));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData definition_data(CompositeAlgoRecordImmutable recordi) => new AlgoData(recordi.AlgoDefinition.Algo, recordi.AlgoDefinition.Name, get_key(recordi));
|
||||
|
||||
public static AlgoData definition_data(CompositeAlgoDefinitionImmutable definitioni) =>
|
||||
new AlgoData(definitioni.Algo, definitioni.Name, get_key(definitioni));
|
||||
|
||||
public static AlgoData? definition_data(Result<CompositeAlgoCreateResult> result) {
|
||||
if (result.success && result.value.Success) {
|
||||
var r = result.value.Record;
|
||||
return new AlgoData(new CustomOrderDefinitionImmutable(r.AlgoDefinition.Algo), r.AlgoDefinition.Name, new AlgoKey(new AlgoPersistedKey(r.Key)));
|
||||
} else return null;
|
||||
}
|
||||
|
||||
// AlgoTemplateRecord(Immutable) template data
|
||||
public static AlgoTemplateData template_data(AlgoTemplateRecord record) => template_data(new AlgoTemplateRecordImmutable(record));
|
||||
public static AlgoTemplateData template_data(AlgoTemplateRecordImmutable recordi) => new AlgoTemplateData(custom_order_definitioni(recordi.AlgoTemplate), recordi.AlgoTemplate.Name, get_key(recordi), definition_data(recordi.AlgoTemplate));
|
||||
|
||||
public static CompositeAlgoDefinition to_definition(AlgoData definition_data) =>
|
||||
new CompositeAlgoDefinition {
|
||||
Name = definition_data.name,
|
||||
Algo = definition_data.value.CloneToMutable()
|
||||
};
|
||||
|
||||
// AlgoTemplateData to_record(i)
|
||||
public static AlgoTemplateRecord to_record(AlgoTemplateData template) {
|
||||
var record = new AlgoTemplateRecord {
|
||||
AlgoTemplate = new AlgoTemplate {
|
||||
Name = template.name,
|
||||
CompositeAlgoParams = new CompositeAlgoDefinition {
|
||||
Name = template.definition.name,
|
||||
Algo = template.value.CloneToMutable()
|
||||
}
|
||||
}
|
||||
};
|
||||
set_key(ref record, template.key);
|
||||
return record;
|
||||
}
|
||||
public static AlgoTemplateRecordImmutable to_recordi(AlgoTemplateData template) => new AlgoTemplateRecordImmutable(to_record(template));
|
||||
|
||||
// AlgoTemplateData to_template(i)
|
||||
public static AlgoTemplate to_template(AlgoTemplateData template) =>
|
||||
new AlgoTemplate {
|
||||
Name = template.name,
|
||||
CompositeAlgoParams = new CompositeAlgoDefinition {
|
||||
Name = template.definition.name,
|
||||
Algo = template.value.CloneToMutable()
|
||||
}
|
||||
};
|
||||
public static AlgoTemplateImmutable to_templatei(AlgoTemplateData template) => new AlgoTemplateImmutable(to_template(template));
|
||||
|
||||
public static AlgoKey get_key(CompositeAlgoRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
public static AlgoKey get_key(AlgoTemplateRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
public static AlgoKey get_key(CompositeAlgoDefinitionImmutable definitioni) => new AlgoKey(new AlgoPersistedKey(definitioni.Name));
|
||||
public static AlgoKey get_key(CustomOrderDefinitionImmutable custom_order_definitioni) => new AlgoKey(custom_order_definitioni.CustomCase);
|
||||
|
||||
public static void set_key(ref AlgoTemplateRecord record, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
switch (key.persisted.mode) {
|
||||
case AlgoPersistedKey.Mode.String:
|
||||
record.Key = key.persisted.id_string; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public static void set_key(ref AlgoServiceRemoveRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
switch (key.persisted.mode) {
|
||||
case AlgoPersistedKey.Mode.String:
|
||||
request.Key = key.persisted.id_string; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export(typeof(IAlgoStudioModel))]
|
||||
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoDefinitionService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
@@ -18,12 +19,12 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
|
||||
[ImportingConstructor]
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
_service = service;
|
||||
this.metadata_service = metadata_service;
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
@@ -35,7 +36,7 @@ namespace mbts.spark {
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
|
||||
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||
@@ -71,10 +72,10 @@ namespace mbts.spark {
|
||||
new AlgoDefinitionImmutable(_template(definition_data));
|
||||
|
||||
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
|
||||
new AlgoData(recordi.Algo.Definition, recordi.Algo.Name, _key(recordi));
|
||||
AlgoData.from_record(recordi);
|
||||
|
||||
static AlgoKey _key(AlgoDefinitionRecordImmutable recordi) =>
|
||||
new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
AlgoKey.from_composite_key(recordi.Key);
|
||||
|
||||
static AlgoDefinitionUpdateRequest _with_key(AlgoDefinitionUpdateRequest request, AlgoKey key) {
|
||||
_set_key(ref request, key);
|
||||
@@ -83,9 +84,10 @@ namespace mbts.spark {
|
||||
|
||||
static void _set_key(ref AlgoDefinitionUpdateRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Dynamic:
|
||||
case AlgoKey.Mode.None: request.Key = 0; break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
@@ -93,7 +95,7 @@ namespace mbts.spark {
|
||||
|
||||
static void _set_key(ref AlgoDefinitionRemoveRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -20,34 +17,13 @@ namespace mbts.spark {
|
||||
bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
}
|
||||
|
||||
class AlgoStudioModelExporter {
|
||||
[ImportingConstructor]
|
||||
AlgoStudioModelExporter(Spark.AlgoServiceWrapper service_wrapper, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
var ui_configuration = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
switch (service_wrapper.mode) {
|
||||
case Spark.AlgoServiceWrapper.Mode.Flat: model = new AlgoStudioFlatDefinitionModel(service_wrapper.flat, meta, admin_model, error_reporter, ui_configuration); break;
|
||||
case Spark.AlgoServiceWrapper.Mode.Hierarchical: model = new AlgoStudioDefinitionTemplateModel(service_wrapper.hierarhical, meta, admin_model, error_reporter, ui_configuration); break;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public IAlgoStudioModel model { get; }
|
||||
}
|
||||
|
||||
static class AlgoStudioModelUtils {
|
||||
public static bool templates_enabled(this IAlgoStudioModel model) =>
|
||||
get_templates_enabled(model.ui_configuration);
|
||||
|
||||
public static bool get_templates_enabled(bts.config.AlgoStudioOptions ui_configuration) =>
|
||||
ui_configuration.add_template_button != bts.config.AlgoStudioOptions.AddTemplateButton.None;
|
||||
|
||||
public static IReadOnlyDictionary<object, object> get_dynamic_resources(bts.config.AlgoStudioOptions ui_configuration) =>
|
||||
new Dictionary<object, object> {
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey, ui_configuration.can_modify_definition_name && ui_configuration.show_algo_toolbar_rename },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey, ui_configuration.can_modify_definition_values && ui_configuration.show_algo_toolbar_save },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey, ui_configuration.show_algo_toolbar_revert },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo },
|
||||
{ AlgoStudioWindow.TemplatesEnabledKey, get_templates_enabled(ui_configuration) }
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo }
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -35,9 +35,9 @@ namespace mbts.spark {
|
||||
internal AlgoDefinitionPersistedMetadata to_persisted() =>
|
||||
id.mode != AlgoKey.Mode.Persisted ?
|
||||
throw new InvalidOperationException("Metadata does not have a persisted key.") :
|
||||
new AlgoDefinitionPersistedMetadata(id.persisted,
|
||||
default_template.mode == AlgoKey.Mode.Persisted ? default_template.persisted : default,
|
||||
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => t.persisted).ToArray(),
|
||||
new AlgoDefinitionPersistedMetadata(new AlgoPersistedKey(id.persisted),
|
||||
default_template.mode == AlgoKey.Mode.Persisted ? new AlgoPersistedKey(default_template.persisted) : default,
|
||||
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => new AlgoPersistedKey(t.persisted)).ToArray(),
|
||||
is_favorite, is_hidden, abbreviation, new NullableColorProto(badge_color));
|
||||
|
||||
public AlgoDefinitionMetadata with(AlgoKey? id = null, AlgoKey? default_template = null, IEnumerable<AlgoKey> favorite_templates = null, bool? is_favorite = null, bool? is_hidden = null, string abbreviation = null, NullableColorProto? badge_color = null) =>
|
||||
|
||||
@@ -83,7 +83,8 @@ namespace mbts.spark {
|
||||
|
||||
public static AlgoDisplayGroup display_group(this AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory.structure.order_type());
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory);
|
||||
case AlgoKey.Mode.Dynamic: return AlgoDisplayGroup.API;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoDefinitionTemplateVM : DisposableViewModelBase, IAlgoStudioContentVM, IEquatable<AlgoDefinitionTemplateVM> {
|
||||
/// <see cref="CustomType"/> is referenced by an x:Static extension in OrderDetailsPopupControl.xaml <see cref="mbts.ui.spark.algo.OrderDetailsPopupControl"/>.
|
||||
/// In most cases, <see cref="mbts.ui.NestedExtension"/> works just like <see cref="System.Windows.Markup.TypeExtension"/>, but for nested types, such as <see cref="CustomTemplatePlaceholderVM"/>.
|
||||
/// However, when the former is used to denote the DataType of a DataTemplate, a strange XamlReader exception is thrown at runtime.
|
||||
/// After spending ~15 minutes investigating what was happening, [John]I decided it was more trouble than it was worth and opted for this workaround.
|
||||
public static readonly Type CustomType = typeof(CustomTemplatePlaceholderVM);
|
||||
readonly OrdersModel _model;
|
||||
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
|
||||
/// Note: the difference between <see cref="EditStack{T}"/> and <see cref="VersionContainer{T}"/> is that the former implements push/pop functionality so the user may undo/redo (like ctrl+z/ctrl+y in text editors),
|
||||
/// while the latter is a lazy delegate-based container for the most recent value (<see cref="VersionContainer{T}.increment_version"/>).
|
||||
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
|
||||
readonly VersionContainer<Snapshot> _version;
|
||||
readonly DeferrableActionWrapper _refresh_on_local_change;
|
||||
readonly Property<IAlgoVM> _algo_root;
|
||||
readonly DelegateProperty<bool> _can_undo, _can_redo, _is_dirty;
|
||||
readonly Property<bool> _is_default, _is_favorite, _is_selected;
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
// maintains the state of each algo-root, so the user may change the definition root back and forth without any loss of state amongst the templates
|
||||
Snapshot _committed;
|
||||
|
||||
public AlgoDefinitionTemplateVM(AlgoDefinitionVM owner, OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = AlgoEditingPermissions.AllowEditValues) {
|
||||
// init core data
|
||||
this.owner = owner;
|
||||
_model = model;
|
||||
key = data.key;
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
// maintain states for different algo roots, so they aren't lost when the user changes the root algo type
|
||||
var algo_map = new Dictionary<AlgoKey, IAlgoVM> {
|
||||
{ data.key, algo_vm_factory(data.value) }
|
||||
};
|
||||
// init backing props
|
||||
_name = backing(nameof(this.name), name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_is_default = backing(nameof(is_default), false);
|
||||
_is_favorite = backing(nameof(is_favorite), false);
|
||||
_is_selected = backing(nameof(is_selected), false);
|
||||
_algo_root = backing(nameof(algo_root), get_algo_root(owner.algo_root));
|
||||
_committed = _version.value;
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
_is_dirty = backing(nameof(is_dirty), () => !_committed.Equals(_version.value));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
set_as_default_cmd = new DelegateCommand(() => {
|
||||
if (!is_default && key.mode == AlgoKey.Mode.Persisted)
|
||||
_model.algo_model.metadata_service.set_as_default(owner.key, key).ignore();
|
||||
});
|
||||
// init visual states
|
||||
// TODO: implement these features in IAlgoMetadataService; until then, set the appropriate visual states.
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Abbreviation.GroupName, VisualStateNames.Abbreviation.NotSupportedForTemplates);
|
||||
_tree_view_vs_model.set_state(VisualStateNames.FavoriteToggleButton.GroupName, VisualStateNames.FavoriteToggleButton.Inactive);
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_edit_stack.push(_committed);
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active);
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active); break;
|
||||
case AlgoKey.Mode.Factory: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive); break;
|
||||
}
|
||||
// set hooks/subscriptions
|
||||
_refresh_on_local_change = new DeferrableActionWrapper(on_local_changed);
|
||||
_dispose = new System.Reactive.Disposables.CompositeDisposable(
|
||||
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
|
||||
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
Disposable.wrap_collection(algo_map.Values)) {
|
||||
() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}
|
||||
};
|
||||
// local methods
|
||||
IAlgoVM algo_vm_factory(CustomOrderDefinitionImmutable def) => OrderTypeVMFactory.create(new AlgoData(def, def.CustomCase.display_name(), OrderDataHelper.get_shallow_factory_key(def)), editing_permissions);
|
||||
IAlgoVM get_algo_root(IAlgoVM owner_root) {
|
||||
var key = new AlgoKey(owner_root.order_type);
|
||||
if (algo_map.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_structure(owner_root.definition);
|
||||
} else {
|
||||
vm = algo_map.GetOrAdd(key, ot => algo_vm_factory(owner_root.definition));
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
void on_local_changed() {
|
||||
_version.increment_version();
|
||||
_edit_stack.push_distinct(_version.value);
|
||||
_is_dirty.refresh();
|
||||
_refresh_undo_redo();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action can_redo_changed, can_undo_changed;
|
||||
public event PropertyChange<IAlgoVM> algo_root_changed { add => _algo_root.Changed += value; remove => _algo_root.Changed -= value; }
|
||||
|
||||
public AlgoDefinitionVM owner { get; }
|
||||
public AlgoKey owner_key => owner.key;
|
||||
public AlgoKey key { get; }
|
||||
public string name { get => _name.Value; set => _name.set(value); }
|
||||
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public bool is_default => _is_default.Value;
|
||||
public bool is_favorite { get => _is_favorite.Value; set => _is_favorite.set(value); }
|
||||
public IAlgoVM algo_root => _algo_root.Value;
|
||||
public virtual bool is_custom_placeholder => false;
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_expanded => false;
|
||||
public bool is_persisted => key.mode == AlgoKey.Mode.Persisted;
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
public ICommand set_as_default_cmd { get; }
|
||||
|
||||
public IObservable<bool> observe_is_dirty() => _is_dirty.observe_current(true);
|
||||
public void undo() => _restore(_edit_stack.try_undo);
|
||||
public void redo() => _restore(_edit_stack.try_redo);
|
||||
|
||||
public void on_meta(bool is_default, bool is_favorite) {
|
||||
_is_default.set(is_default);
|
||||
_is_favorite.set(is_favorite);
|
||||
}
|
||||
|
||||
public AlgoData create_def_data() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
|
||||
public AlgoTemplateData create() =>
|
||||
create(owner.create_algo_def());
|
||||
|
||||
public AlgoTemplateData create(AlgoData record) {
|
||||
if (!OrderDataHelper.EqualsHelpers.structure_equals(algo_root.definition, record.value))
|
||||
throw new InvalidOperationException("AlgoTemplate's algo structure does not match that of its parent.");
|
||||
return new AlgoTemplateData(algo_root.definition, name, key, record);
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM to_persisted(AlgoDefinitionVM persisted_owner, AlgoData template) =>
|
||||
new AlgoDefinitionTemplateVM(persisted_owner, _model, template);
|
||||
|
||||
public void load_committed() => _load_from(_committed.name, _committed.definition, true);
|
||||
|
||||
public void load_from(AlgoTemplateData value) {
|
||||
if (key.mode != AlgoKey.Mode.Persisted)
|
||||
throw new InvalidOperationException("AlgoStructure has not been saved yet.");
|
||||
else if (value.key != key)
|
||||
throw new InvalidOperationException("Value id does not match this id.");
|
||||
_load_from(value.name, value.value, true);
|
||||
}
|
||||
|
||||
public bool Equals(AlgoDefinitionTemplateVM other) => other != null && key == other.key;
|
||||
public override bool Equals(object obj) => Equals(obj as AlgoDefinitionTemplateVM);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
void _load_from(string new_name, CustomOrderDefinitionImmutable new_def, bool update_committed) {
|
||||
using (_refresh_on_local_change.defer_execution()) {
|
||||
name = new_name;
|
||||
algo_root.load_from(new_def);
|
||||
}
|
||||
if (update_committed) {
|
||||
_committed = _version.value;
|
||||
_is_dirty.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_undo_redo() {
|
||||
_can_redo.refresh();
|
||||
_can_undo.refresh();
|
||||
}
|
||||
|
||||
void _restore(TryGetValue<Snapshot> try_get_snapshot) {
|
||||
while (try_get_snapshot(out Snapshot next_state)) {
|
||||
if (!_version.value.Equals(next_state)) {
|
||||
_load_from(next_state.name, next_state.definition, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Snapshot _snapshot() =>
|
||||
new Snapshot(name, abbreviation, algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()), is_default, is_favorite);
|
||||
|
||||
struct Snapshot : IEquatable<Snapshot> {
|
||||
public readonly string name, abbreviation;
|
||||
public readonly CustomOrderDefinitionImmutable definition;
|
||||
public readonly bool is_default, is_favorite;
|
||||
|
||||
public Snapshot(string name, string abbreviation, CustomOrderDefinitionImmutable definition, bool is_default, bool is_favorite) {
|
||||
this.name = name;
|
||||
this.abbreviation = abbreviation;
|
||||
this.definition = definition;
|
||||
this.is_default = is_default;
|
||||
this.is_favorite = is_favorite;
|
||||
}
|
||||
|
||||
public bool Equals(Snapshot other) =>
|
||||
name == other.name &&
|
||||
abbreviation == other.abbreviation &&
|
||||
is_default == other.is_default &&
|
||||
is_favorite == other.is_favorite &&
|
||||
OrderDataHelper.EqualsHelpers.equals(definition, other.definition);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
EqualityComparer.safe_aggregate_hash(name),
|
||||
EqualityComparer.safe_aggregate_hash(abbreviation),
|
||||
is_default.GetHashCode(),
|
||||
is_favorite.GetHashCode(),
|
||||
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.GetHashCode(definition));
|
||||
|
||||
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
|
||||
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
public class CustomTemplatePlaceholderVM : AlgoDefinitionTemplateVM {
|
||||
const string _custom_name = "(Custom)";
|
||||
|
||||
public CustomTemplatePlaceholderVM(AlgoDefinitionVM owner, OrdersModel model, CustomOrderDefinitionImmutable definition)
|
||||
: base(owner, model, new AlgoData(definition, _custom_name, new AlgoKey(Guid.NewGuid())), AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues) {
|
||||
_edit_stack.reset(_snapshot());
|
||||
}
|
||||
|
||||
public override bool is_custom_placeholder => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts.api.core;
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
@@ -6,7 +6,6 @@ using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
@@ -18,7 +17,6 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
|
||||
|
||||
@@ -36,40 +34,27 @@ namespace mbts.spark {
|
||||
public static readonly CustomOrderDefinitionImmutable new_algo_def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg);
|
||||
readonly OrdersModel _model;
|
||||
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey> _templates = new ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey>(t => t.key);
|
||||
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
|
||||
readonly VersionContainer<Snapshot> _version;
|
||||
readonly Property<bool> _is_expanded, _is_selected;
|
||||
readonly Property<IAlgoVM> _algo_root, _visible_algo_root;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
|
||||
readonly DelegateProperty<bool> _is_favorite, _is_dirty, _can_undo, _can_redo;
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
|
||||
readonly ObservableAppendCollection<AlgoDefinitionTemplateVM> _templates_with_custom;
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
mbts.ui.spark.AlgoContentTemplateSelector.OrderKind IAlgoSelector.order_kind =>
|
||||
new mbts.ui.spark.AlgoContentTemplateSelector.OrderKind(string.Empty, string.Empty);
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
AlgoDefinitionMetadata _metadata;
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
if (connect)
|
||||
rv.connect();
|
||||
return rv;
|
||||
}
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
|
||||
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
@@ -81,20 +66,8 @@ namespace mbts.spark {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
else if (model.algo_model.templates_enabled()) {
|
||||
resources.Add(_templates.Subscribe((s, e) => {
|
||||
if (_templates.Count == 0) {
|
||||
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
} else {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
}
|
||||
}));
|
||||
}
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
|
||||
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
|
||||
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
@@ -102,87 +75,80 @@ namespace mbts.spark {
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_selected_format = backing(nameof(selected_format), init_format);
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
|
||||
_is_expanded = backing(nameof(is_expanded), true);
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : AlgoData.from_custom_def(def_data.value);
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
// only composite/studio algos can change root
|
||||
if (key.mode == AlgoKey.Mode.Factory || key.mode == AlgoKey.Mode.Dynamic) {
|
||||
_algo_root.Changing += (oldval, newval) =>
|
||||
Log.assert(newval != null && newval.id == key);
|
||||
}
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
|
||||
// load initial state
|
||||
init_async();
|
||||
async void init_async() {
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
|
||||
if (_model.algo_model.templates_enabled()) {
|
||||
resources.Add(_selected_template.observe_current(true).Subscribe(t => _visible_algo_root.set(t?.algo_root)));
|
||||
} else {
|
||||
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
|
||||
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
|
||||
// set hooks/subscriptions
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
resources.AddRange(
|
||||
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}));
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_metadata = new AlgoDefinitionMetadata(key);
|
||||
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
// init tree view item visual state
|
||||
_tree_view_vs_model.set_state(VisualStateNames.TreeViewBadge.GroupName, conf.show_badges_in_tree_view ? VisualStateNames.TreeViewBadge.Active : VisualStateNames.TreeViewBadge.Inactive);
|
||||
// set hooks/subscriptions
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
resources.AddRange(
|
||||
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
ObservableAutoWrapper.connect_item_hooks(templates, t => new CompositeDisposable(_any_templates_dirty_monitor.join(t), t), true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}));
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_metadata = new AlgoDefinitionMetadata(key);
|
||||
resources.Add(_template_update_buffer.OnDispatcher().Subscribe(_on_templates));
|
||||
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
algo_root.load_from(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
// local methods
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
@@ -209,7 +175,6 @@ namespace mbts.spark {
|
||||
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
|
||||
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
|
||||
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
|
||||
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
|
||||
public int? favorite_index {
|
||||
get => _favorite_index;
|
||||
set {
|
||||
@@ -222,34 +187,12 @@ namespace mbts.spark {
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_factory_definition => key.mode == AlgoKey.Mode.Factory;
|
||||
public AlgoDisplayGroup group {
|
||||
get {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (algo_root != null)
|
||||
return algo_root.group;
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
}
|
||||
return AlgoDisplayGroup.BTS;
|
||||
}
|
||||
}
|
||||
public AlgoDisplayGroup group => key.display_group();
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
||||
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
||||
public Brush badge_fill => _badge_fill;
|
||||
public ObservableCollection<AlgoDefinitionTemplateVM> templates => _templates;
|
||||
/// <see cref="templates_with_custom"/> has the same elements as <see cref="templates"/>, plus the custom-placeholder-item, which is always at index 0.
|
||||
/// The custom-placeholder supports legacy behavior in the ladder/ticket algo popup by allowing the user to manipulate the algo-structure (e.g. change an iceberg-limit order to iceberg-lean-cover-offset etc.).
|
||||
/// However, the custom-placeholder item cannot be used to create/save algo-templates.
|
||||
/// Algo-templates are just a set of values used to populate a concrete algo-structure; the 'structures' can be created in the AlgoStudio (<see cref="AlgoStudioWindow"/>).
|
||||
/// If we were to strictly follow this paradigm, the user would no longer be able to freely change the algo-structure in the ladder/ticket popup (they would be forced to open up a new <see cref="AlgoStudioWindow"/>, and we know how much they hate more clicks!).
|
||||
/// Some users might consider this a regression. The custom-placeholder-item addresses that concern.
|
||||
public ObservableAppendCollection<AlgoDefinitionTemplateVM> templates_with_custom => _templates_with_custom;
|
||||
public ListCollectionView templates_view { get; }
|
||||
// format
|
||||
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
|
||||
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
|
||||
@@ -324,24 +267,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// idempotent
|
||||
public void prepend_custom_template() {
|
||||
if (_templates_with_custom.appended_front.Count == 0)
|
||||
_templates_with_custom.insert_front(new AlgoDefinitionTemplateVM.CustomTemplatePlaceholderVM(this, _model, algo_root.definition));
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM template_factory() =>
|
||||
new AlgoDefinitionTemplateVM(this, _model, algo_root.create_def_data().with(name: new_template_name)) {
|
||||
is_selected = true
|
||||
};
|
||||
|
||||
public IObservable<bool> subscribe_is_definition_dirty() => _is_dirty.observe_current(true);
|
||||
public IObservable<bool> subscribe_any_templates_dirty() => _any_templates_dirty_monitor;
|
||||
public IObservable<bool> subscribe_is_definition_or_any_templates_dirty() =>
|
||||
Observable.Merge(
|
||||
subscribe_is_definition_dirty().Select(v => v || _any_templates_dirty_monitor.is_above_threshold),
|
||||
subscribe_any_templates_dirty().Select(v => v || is_dirty))
|
||||
.DistinctUntilChanged();
|
||||
|
||||
public void undo() => _restore(_edit_stack.try_undo);
|
||||
public void redo() => _restore(_edit_stack.try_redo);
|
||||
@@ -350,20 +276,9 @@ namespace mbts.spark {
|
||||
_assign_from_data(_committed.Value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
/// <summary>
|
||||
/// Subscribes to the templates source for this definition if it has been persisted (subscription requires a persistant id); does nothing if this definition has not been persisted, or if it is already subscribed (idempotent).
|
||||
/// </summary>
|
||||
public void connect() {
|
||||
if (!_connected) {
|
||||
_connected = true;
|
||||
concat_disposable(_model.algo_model.subscribe_definition_templates(key).OnDispatcher().Subscribe(_template_update_buffer.OnNext));
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
var key = AlgoKey.from_custom_def(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_from(def);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
@@ -387,7 +302,7 @@ namespace mbts.spark {
|
||||
case 1: load_def(def_list[0]); break;
|
||||
default:
|
||||
var def = def_list[0];
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
var key = AlgoKey.from_custom_def(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_many(def_list);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
@@ -417,7 +332,7 @@ namespace mbts.spark {
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
|
||||
|
||||
public AlgoDefinitionMetadata create_metadata() =>
|
||||
_metadata.with(abbreviation: abbreviation, badge_color: new NullableColorProto(custom_badge_color.get_nullable()));
|
||||
@@ -446,7 +361,7 @@ namespace mbts.spark {
|
||||
|
||||
void _assign_from_data(string name, CustomOrderDefinitionImmutable definition) {
|
||||
this.name = name;
|
||||
algo_root = _algo_roots.GetOrAdd(OrderDataHelper.get_shallow_factory_key(definition), _ => _mk_algo_root(definition));
|
||||
algo_root = _algo_roots.GetOrAdd(AlgoKey.from_custom_def(definition), _ => _mk_algo_root(definition));
|
||||
algo_root.load_from(definition);
|
||||
}
|
||||
|
||||
@@ -461,18 +376,12 @@ namespace mbts.spark {
|
||||
public Snapshot current_value => _version.value;
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
OrderTypeVMFactory.create(AlgoData.from_custom_def(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
_abbreviation.set(metadata.abbreviation);
|
||||
custom_badge_color.set_nullable(metadata.badge_color);
|
||||
foreach (var t in templates) {
|
||||
if (t.key.mode == AlgoKey.Mode.Persisted)
|
||||
t.on_meta(t.key == metadata.default_template, metadata.favorite_templates.Contains(t.key));
|
||||
else
|
||||
t.on_meta(false, false);
|
||||
}
|
||||
_version.increment_version();
|
||||
if (_edit_stack.size == 1 && _edit_stack.pos == 1) {
|
||||
// this is a hack to account for the metadata being part of the committed state,
|
||||
@@ -485,22 +394,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _on_templates(TableUpdate<AlgoTemplateData, AlgoKey> table) {
|
||||
foreach (var rec in table.removed)
|
||||
_templates.Remove(rec.key);
|
||||
foreach (var rec in table.changed)
|
||||
_templates[rec.key].load_from(rec);
|
||||
foreach (var rec in table.added) {
|
||||
if (_templates.TryGetValue(rec.key, out AlgoDefinitionTemplateVM tmp)) {
|
||||
tmp.load_from(rec);
|
||||
} else {
|
||||
tmp = new AlgoDefinitionTemplateVM(this, _model, new AlgoData(rec.value, rec.name, rec.key));
|
||||
tmp.on_meta(tmp.key == _metadata.default_template, _metadata.favorite_templates.Contains(tmp.key));
|
||||
_templates.Add(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_undo_redo() {
|
||||
_can_undo.refresh();
|
||||
_can_redo.refresh();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -14,6 +13,7 @@ using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
@@ -37,29 +37,30 @@ namespace mbts.spark {
|
||||
});
|
||||
}
|
||||
|
||||
class AlgoStudioVM : DisposableViewModelBase, IHandleSelectedItemChanged {
|
||||
class AlgoStudioVM : DisposableViewModelBase {
|
||||
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<AlgoDefinitionVM> _selected_definition;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
|
||||
readonly Property<IAlgoVM> _editable_content;
|
||||
readonly DelegateProperty<string> _save_cmd_tooltip, _revert_cmd_tooltip;
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly ListCollectionView<AlgoDefinitionVM> _definitions_view;
|
||||
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_update_buffer;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_or_any_templates_dirty());
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_dirty());
|
||||
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _persisted_meta = new Dictionary<AlgoKey, AlgoDefinitionMetadata>();
|
||||
|
||||
public AlgoStudioVM(OrdersModel model) {
|
||||
_model = model;
|
||||
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
|
||||
_editable_content = backing(nameof(editable_content), subscribe_definition().SelectManyInSeries(def => def?.observe_algo_root()).OnDispatcher());
|
||||
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
|
||||
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
};
|
||||
definitions_view.Filter = d => !(d.key.mode == AlgoKey.Mode.Persisted && _persisted_meta.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden);
|
||||
// add/clone
|
||||
add_definition_cmd = new DelegateCommand(add_definition);
|
||||
clone_selected_definition_cmd = new DelegateCommand(clone_selected_definition);
|
||||
// save
|
||||
var save_cmd = new ObservantAsyncCommand<IAlgoStudioContentVM>(save_async);
|
||||
this.save_cmd = save_cmd;
|
||||
@@ -71,13 +72,18 @@ namespace mbts.spark {
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, true, _studio_formats))),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
|
||||
var orig_selected_def = selected_definition;
|
||||
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
|
||||
if (selected_definition == null)
|
||||
selected_definition = definitions.FirstOrDefault();
|
||||
}),
|
||||
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
|
||||
_persisted_meta = meta;
|
||||
definitions_view.Refresh();
|
||||
}),
|
||||
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
@@ -85,77 +91,64 @@ namespace mbts.spark {
|
||||
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
|
||||
// local methods
|
||||
string get_revert_tooltip() {
|
||||
if (content_source is AlgoDefinitionTemplateVM tmp) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Revert changes to '{tmp.name}'" : "No changes to revert";
|
||||
}
|
||||
} else if (content_source is AlgoDefinitionVM def) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Revert changes to '{def.name}' templates" : "No changes to revert";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
var def = selected_definition;
|
||||
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
|
||||
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
|
||||
null;
|
||||
}
|
||||
async Task save_async(IAlgoStudioContentVM content) {
|
||||
if (content is AlgoDefinitionTemplateVM tmp) {
|
||||
await save_template(tmp);
|
||||
} else if (content is AlgoDefinitionVM def) {
|
||||
if (content is AlgoDefinitionVM def)
|
||||
await save_definition(def);
|
||||
}
|
||||
}
|
||||
string get_save_tooltip() {
|
||||
if (content_source is AlgoDefinitionTemplateVM tmp) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return tmp.is_dirty ? $"Save '{tmp.name}'" : $"Cannot Save '{tmp.name}'";
|
||||
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Save changes to '{tmp.name}'" : "No changes to save";
|
||||
}
|
||||
} else if (content_source is AlgoDefinitionVM def) {
|
||||
var def = selected_definition;
|
||||
if (def != null) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void add_definition() =>
|
||||
add_definition_common(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
|
||||
|
||||
void clone_selected_definition() {
|
||||
if (selected_definition == null || selected_definition.algo_root == null) {
|
||||
add_definition();
|
||||
} else {
|
||||
add_definition_common(
|
||||
selected_definition.algo_root.definition,
|
||||
string.IsNullOrEmpty(selected_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{selected_definition.name} - Copy");
|
||||
}
|
||||
}
|
||||
|
||||
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
|
||||
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
|
||||
_definitions.Add(new_def);
|
||||
selected_definition = new_def;
|
||||
request_view_focus_name?.Invoke(new_def);
|
||||
}
|
||||
}
|
||||
|
||||
event Action<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> _selected_changed;
|
||||
public event Action<AlgoDefinitionVM> request_view_focus_name;
|
||||
|
||||
public ObservableCollection<AlgoDefinitionVM> definitions => _definitions;
|
||||
public ObservableCollection<string> content_visual_states { get; } = new ObservableCollection<string>();
|
||||
public AlgoDefinitionVM selected_definition { get => _selected_definition.Value; set => _selected_definition.set(value); }
|
||||
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
|
||||
public IAlgoStudioContentVM content_source => (IAlgoStudioContentVM)selected_template ?? selected_definition;
|
||||
public IAlgoVM editable_content => _editable_content.Value;
|
||||
public ICollectionView<AlgoDefinitionVM> definitions_view => _definitions_view;
|
||||
public ICommand add_definition_cmd { get; }
|
||||
public ICommand clone_selected_definition_cmd { get; }
|
||||
public ICommand save_cmd { get; }
|
||||
public ICommand revert_cmd { get; }
|
||||
public string save_cmd_tooltip => _save_cmd_tooltip.Value;
|
||||
public string revert_cmd_tooltip => _revert_cmd_tooltip.Value;
|
||||
|
||||
public void add_new_definition() =>
|
||||
_add_new_definition(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
|
||||
|
||||
public void add_new_definition_clone(AlgoDefinitionVM src_definition) {
|
||||
if (src_definition == null || src_definition.algo_root == null) {
|
||||
add_new_definition();
|
||||
} else {
|
||||
_add_new_definition(src_definition.algo_root.definition, string.IsNullOrEmpty(src_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{src_definition.name} - Copy");
|
||||
}
|
||||
}
|
||||
|
||||
void _add_new_definition(CustomOrderDefinitionImmutable def, string name) =>
|
||||
_definitions.Add(AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats));
|
||||
|
||||
public StartupData create() => new StartupData();
|
||||
public void load_from(StartupData state) { }
|
||||
public IObservable<AlgoDefinitionVM> subscribe_definition() => _selected_definition.observe_current(true);
|
||||
public IObservable<AlgoDefinitionTemplateVM> subscribe_template() => _selected_template.observe_current(true);
|
||||
public IObservable<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> observe_selected() =>
|
||||
Observable.Create<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)>(o => {
|
||||
_selected_changed += o.OnNext;
|
||||
return Disposable.Create(() => _selected_changed -= o.OnNext);
|
||||
});
|
||||
public IObservable<bool> subscribe_dirty() => _any_dirty_monitor;
|
||||
|
||||
public async Task remove_definition(AlgoDefinitionVM definition) {
|
||||
@@ -163,87 +156,43 @@ namespace mbts.spark {
|
||||
await _model.algo_model.remove_definition(definition.key);
|
||||
} else {
|
||||
// This is a new definition, which hasn't been saved/persisted yet. No need to call the server.
|
||||
definition.templates.Clear();
|
||||
definitions.Remove(definition);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task remove_template(AlgoDefinitionTemplateVM template) {
|
||||
if (template.key.mode == AlgoKey.Mode.Persisted) {
|
||||
await _model.algo_model.remove_template(template.key);
|
||||
} else {
|
||||
// This is a new template, which hasn't been saved/persisted yet. No need to call the server.
|
||||
template.owner.templates.Remove(template);
|
||||
}
|
||||
}
|
||||
public Task save_definition(AlgoDefinitionVM definition) =>
|
||||
_save(new[] { definition });
|
||||
|
||||
public async Task save_definition(AlgoDefinitionVM definition) {
|
||||
using (_save_op()) {
|
||||
var newly_persisted_keys = await AlgoPersistenceHelper.persist_definition(_model, _definitions, definition);
|
||||
AlgoKey pkey;
|
||||
switch (definition.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
if (!newly_persisted_keys.TryGetValue(definition.key, out pkey))
|
||||
return;
|
||||
break;
|
||||
case AlgoKey.Mode.Persisted:
|
||||
pkey = definition.key;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
await _model.algo_model.metadata_service.update_metadata(pkey, _ => definition.create_metadata().with(id: pkey));
|
||||
}
|
||||
}
|
||||
public Task save_all() =>
|
||||
_save(definitions);
|
||||
|
||||
public async Task save_template(AlgoDefinitionTemplateVM template) {
|
||||
using (_save_op())
|
||||
await AlgoPersistenceHelper.persist_template(_model, _definitions, template);
|
||||
}
|
||||
|
||||
public async Task save_all() =>
|
||||
await _save(definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
|
||||
|
||||
async Task _save(IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
|
||||
using (_save_op())
|
||||
await AlgoPersistenceHelper.persist(_model, definition_templates);
|
||||
}
|
||||
|
||||
IDisposable _save_op() {
|
||||
int? def_index = selected_definition == null ? (int?)null : _definitions.IndexOf(selected_definition);
|
||||
int? tmp_index = selected_template == null ? (int?)null : selected_definition.templates.IndexOf(selected_template);
|
||||
return new CompositeDisposable(_definitions_update_buffer.buffer_updates()) {
|
||||
() => {
|
||||
if (def_index.HasValue) {
|
||||
var def = _definitions[def_index.Value];
|
||||
if (tmp_index.HasValue) {
|
||||
var tmp = def.templates[tmp_index.Value];
|
||||
tmp.is_selected = true;
|
||||
} else def.is_selected = true;
|
||||
async Task _save(IEnumerable<AlgoDefinitionVM> definitions) {
|
||||
var orig_selected = selected_definition;
|
||||
var orig_selected_index = orig_selected == null ? -1 : this.definitions.IndexOf(orig_selected);
|
||||
using (_definitions_update_buffer.buffer_updates()) {
|
||||
var def_list = definitions as IList<AlgoDefinitionVM> ?? definitions.ToList();
|
||||
await Task.WhenAll(definitions.Select(async d => {
|
||||
var def = await _model.algo_model.save_definition(d.create_algo_def());
|
||||
if (def.HasValue) {
|
||||
// don't forget to save metadata
|
||||
await _model.algo_model.metadata_service.update_metadata(def.Value.key, _ => d.create_metadata().with(def.Value.key));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Despite the existence of these properties: <see cref="AlgoDefinitionVM.is_selected"/>, and <see cref="AlgoDefinitionTemplateVM.is_selected"/>,
|
||||
/// <see cref="AlgoStudioVM"/> still implements <see cref="IHandleSelectedItemChanged"/> to update the properties that depend on the <see cref="System.Windows.Controls.TreeView.SelectedItem"/>.
|
||||
/// [John]I am going to Uganda in 3 days (today is 1/3/2019), and decided against removing this legacy-dependency at the last minute.
|
||||
/// Fwiw, <see cref="AlgoDefinitionVM.is_selected"/> and <see cref="AlgoDefinitionTemplateVM.is_selected"/> are bound to <see cref="System.Windows.Controls.TreeViewItem.IsSelected"/>.
|
||||
/// They serve to prevent a quirky default behavior in TreeView: double clicking any TreeViewItem collapses the entire TreeView.
|
||||
/// This behavior wasn't just quirky either, it actually conflicted with the double-click-to-set-template-as-default behavior.
|
||||
/// (for details, <see cref="mbts.ui.spark.AlgoStudioView.EnableToggleDefaultOnDoubleClickProperty"/>)
|
||||
void IHandleSelectedItemChanged.on_selected_item_changed(object old_value, object new_value) {
|
||||
if (new_value is AlgoDefinitionVM def) {
|
||||
selected_definition = def;
|
||||
selected_template = null;
|
||||
} else if (new_value is AlgoDefinitionTemplateVM templ) {
|
||||
selected_definition = templ.owner;
|
||||
selected_template = templ;
|
||||
} else {
|
||||
selected_definition = null;
|
||||
selected_template = null;
|
||||
return def;
|
||||
})).ContinueOnDispatcher(t => {
|
||||
if (t.Status != TaskStatus.RanToCompletion) {
|
||||
Log.Ui.debug_fatal($"Algo Studio error saving definitions: unexpected Task.Status '{t.Status}'.");
|
||||
return;
|
||||
}
|
||||
var persisted_data = t.Result;
|
||||
Log.assert(persisted_data.Length == def_list.Count);
|
||||
for (int i = 0; i < persisted_data.Length; i++) {
|
||||
if (!def_list[i].key.is_persisted && persisted_data[i].HasValue)
|
||||
_definitions.Replace(def_list[i].key, def_list[i].to_persisted(persisted_data[i].Value));
|
||||
}
|
||||
if (selected_definition != orig_selected && orig_selected_index >= 0 && orig_selected_index < this.definitions.Count)
|
||||
selected_definition = this.definitions[orig_selected_index];
|
||||
});
|
||||
}
|
||||
_selected_changed?.Invoke((selected_definition, selected_template));
|
||||
RaisePropertyChanged(nameof(content_source));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,6 @@ namespace mbts.spark {
|
||||
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
|
||||
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
|
||||
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
|
||||
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
|
||||
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
|
||||
|
||||
IDisposable _set_hooks(AlgoDefinitionVM vm) {
|
||||
|
||||
+3
@@ -513,6 +513,9 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
IsEnabled="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}, Converter={StaticResource NotConverter}}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
</AdornerDecorator>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace mbts.spark {
|
||||
public readonly int column_count;
|
||||
|
||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count)
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => k.persisted), column_count) { }
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)), column_count) { }
|
||||
|
||||
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count) {
|
||||
_favorite_algos = favorite_algos;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -68,8 +67,7 @@ namespace bts.shell {
|
||||
// profile was deleted
|
||||
id = 0;
|
||||
} else {
|
||||
var curr_profile = _fit_to_sec(result.value, vm.security.ContractCase);
|
||||
if (!s.Equals(curr_profile)) {
|
||||
if (!s.Equals(result.value)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
@@ -79,7 +77,7 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
int _ladder_count = 0;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings last_value, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
// on_shell_window_vm should behave like an event (do not set to null)
|
||||
@@ -95,12 +93,11 @@ namespace bts.shell {
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
|
||||
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
|
||||
state_settings?.sanitize();
|
||||
last_value?.sanitize();
|
||||
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
|
||||
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
|
||||
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
|
||||
bool initialized = false, needs_resync = false;
|
||||
LadderSettings last_value = fit_to_sec(state_settings);
|
||||
LadderVM vm = null;
|
||||
var settings_subject = new Subject<LadderSettings>();
|
||||
var settings_source = Observable.Create<LadderSettings>(o => {
|
||||
@@ -130,9 +127,6 @@ namespace bts.shell {
|
||||
lv.Resources[kv.Key] = kv.Value;
|
||||
return lv;
|
||||
// local methods
|
||||
LadderSettings fit_to_sec(LadderSettings s) =>
|
||||
_fit_to_sec(s, md.security.ContractCase);
|
||||
|
||||
void on_settings_invalidated() =>
|
||||
needs_resync = true;
|
||||
|
||||
@@ -159,7 +153,7 @@ namespace bts.shell {
|
||||
if (!last_value.Equals(vm.get_save_state())) {
|
||||
settings_id = 0;
|
||||
} else {
|
||||
settings_subject.OnNext(last_value = fit_to_sec(profile.value));
|
||||
settings_subject.OnNext(last_value = profile.value);
|
||||
}
|
||||
} else {
|
||||
// profile was deleted; keep the current settings and switch to the 'Local' profile.
|
||||
@@ -169,7 +163,7 @@ namespace bts.shell {
|
||||
initialized = true;
|
||||
if (settings_id != 0) {
|
||||
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
last_value = fit_to_sec(profile.value);
|
||||
last_value = profile.value;
|
||||
} else {
|
||||
settings_id = 0;
|
||||
}
|
||||
@@ -179,13 +173,13 @@ namespace bts.shell {
|
||||
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
|
||||
// In the meantime, simply use the first match.
|
||||
if (lp.security_criteria.includes(md.security)) {
|
||||
last_value = fit_to_sec(lp.value);
|
||||
last_value = lp.value;
|
||||
settings_id = lp.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_value == null)
|
||||
last_value = fit_to_sec(_settings_hub.current.ladder);
|
||||
last_value = _settings_hub.current.ladder;
|
||||
}
|
||||
settings_subject.OnNext(last_value);
|
||||
}
|
||||
@@ -201,7 +195,7 @@ namespace bts.shell {
|
||||
svm.load(s);
|
||||
return svm;
|
||||
},
|
||||
fit_to_sec,
|
||||
_ => _,
|
||||
vm.get_save_state(),
|
||||
UserSettings.FileExtensions.LADDER,
|
||||
settings_id);
|
||||
@@ -283,20 +277,6 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
|
||||
LadderSettings _fit_to_sec(LadderSettings s, Security.ContractOneofCase sec_type) {
|
||||
if (s == null) return null;
|
||||
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
|
||||
if (algo_key.mode == AlgoKey.Mode.Factory) {
|
||||
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
|
||||
if (fitted_algo.HasValue) {
|
||||
// be sure to clone ('s' may be shared)
|
||||
s = SparkUtils.clone_proto(s);
|
||||
s.default_algo = AlgoID.from_custom_def(fitted_algo.Value);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
readonly struct LadderConfig {
|
||||
public readonly uint max_open_ladders;
|
||||
public readonly bool ladders_on_threads, open_algo_popup_on_select, use_algo_param_grid;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<double> _font_size, _grid_row_height;
|
||||
readonly Property<Brush> _foreground, _background, _hoverbackground, _border;
|
||||
readonly Property<bool> _has_multiple_orders_at_same_level, _is_hedge_order;
|
||||
readonly Property<CustomOrderDefinition.CustomOneofCase> _algo_type;
|
||||
@@ -64,6 +64,7 @@ namespace mbts.spark {
|
||||
_font_weight = backing(nameof(font_weight), owner.settings, get_font_weight, get_font_weight(fs));
|
||||
_font_size = backing(nameof(font_size), owner.settings, get_font_size, get_font_size(fs));
|
||||
_foreground = backing(nameof(foreground), owner.settings, get_foreground, get_foreground(fs));
|
||||
_grid_row_height = backing(nameof(grid_row_height), owner.settings, get_row_height, get_row_height(fs));
|
||||
_background = backing(nameof(background), _color_bg_brush);
|
||||
_hoverbackground = backing(nameof(hoverbackground), _color_bg_brush);
|
||||
_border = backing(nameof(border), _color_bg_brush);
|
||||
@@ -75,6 +76,7 @@ namespace mbts.spark {
|
||||
FontWeight get_font_weight(LadderSettings s) => ui.utility.text.TextUtils.from_open_type_weight_safe(s.orders_column.font_weight);
|
||||
double get_font_size(LadderSettings s) => s.orders_column.font_size;
|
||||
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
|
||||
double get_row_height(LadderSettings s) => s.row_height;
|
||||
//
|
||||
add_order(op);
|
||||
|
||||
@@ -246,6 +248,7 @@ namespace mbts.spark {
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public double font_size => _font_size.Value;
|
||||
public double grid_row_height => _grid_row_height.Value;
|
||||
public Brush foreground => _foreground.Value;
|
||||
public Brush background { get => _background.Value; private set => _background.Value = value; }
|
||||
public Brush hoverbackground { get => _hoverbackground.Value; private set => _hoverbackground.Value = value; }
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace mbts.spark {
|
||||
c.PropertyChanged -= on_prop_changed;
|
||||
}
|
||||
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, false);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
@@ -396,7 +396,7 @@ namespace mbts.spark {
|
||||
clone.default_account = default_account.Value.id;
|
||||
// - algo
|
||||
clone.default_algo = default_algo != null ?
|
||||
AlgoID.from_algo_key(default_algo.key, _model.dynamic_helper.dynamic_type_index) :
|
||||
AlgoID.from_algo_key(default_algo.key) :
|
||||
(_desired_algo ?? LadderSettings.Default.default_algo);
|
||||
// - components
|
||||
clone.auto_clear = auto_clear;
|
||||
@@ -453,7 +453,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _try_select_desired_algo(bool bypass_setter = false) {
|
||||
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value, _model.dynamic_helper.dynamic_type_index), out var vm)) {
|
||||
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value), out var vm)) {
|
||||
_desired_algo = null;
|
||||
_default_algo.bypass_setter(vm);
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ namespace mbts.spark {
|
||||
if (order_properties_vm.selected_account.HasValue)
|
||||
rv.default_account = order_properties_vm.selected_account.Value.id;
|
||||
if (order_properties_vm.selected_algo_definition != null)
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key, _model.order_gateway.dynamic_helper.dynamic_type_index);
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
<converters:ResponsiveTextConverter x:Key="OpenTextConverter" LongText="Open:" ShortText="O:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="SettleTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="VolumeTextConverter" LongText="Volume:" ShortText="V:" Threshold="99" />
|
||||
<converters:DoubleSubtractionConverter x:Key="DoubleSubtractionConverter" Value="4" />
|
||||
<converters:DoubleSubtractionConverter x:Key="HeightDoubleSubtractionConverter" Value="6" />
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
<Setter Property="Background" Value="{mbts:BTSColor header}"/>
|
||||
<Setter Property="Foreground" Value="{mbts:BTSColor header_text}"/>
|
||||
@@ -579,7 +581,9 @@
|
||||
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||
<Setter Property="Foreground" Value="{Binding foreground}"/>
|
||||
<Setter Property="TextLines" Value="{Binding text_lines}" />
|
||||
<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>
|
||||
<!--<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>-->
|
||||
<Setter Property="Width" Value="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type spark:LadderOrdersHost}}, Converter={StaticResource DoubleSubtractionConverter}}" />
|
||||
<Setter Property="Height" Value="{Binding grid_row_height, Converter={StaticResource HeightDoubleSubtractionConverter}}" />
|
||||
<Setter Property="HasMultipleOrdersAtLevel" Value="{Binding has_multiple_orders_at_same_level}" />
|
||||
<Setter Property="IsHedgeOrder" Value="{Binding is_hedge_order}" />
|
||||
<Setter Property="AlgoType" Value="{Binding algo_type}" />
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurity> filter { get; }
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace mbts.spark {
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
Add(security_column = new SecurityColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, SecurityImmutable, SecurityCriteria>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, SecurityImmutable, SecurityCriteria>(
|
||||
order_securities,
|
||||
ort => ort.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
ort => ort.order.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
nameof(OrderBookRowVM.security),
|
||||
@@ -85,9 +85,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Symbol,
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ort => ort.security?.Symbol ?? string.Empty,
|
||||
ort => ort.order.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)
|
||||
),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
@@ -98,8 +98,8 @@ namespace mbts.spark {
|
||||
"Time",
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.created(),
|
||||
new TimeFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.created(),
|
||||
nameof(OrderBookRowVM.time_created)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.time_created)));
|
||||
@@ -109,8 +109,8 @@ namespace mbts.spark {
|
||||
"Last Change",
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_change(),
|
||||
new TimeFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_change(),
|
||||
nameof(OrderBookRowVM.last_change)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.last_change)));
|
||||
@@ -120,9 +120,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.User,
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
ort => ort.order.user(),
|
||||
ort => ort.order.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
_wrap_comparer(r => r.user)));
|
||||
@@ -132,9 +132,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Account,
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string, string[]>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
ort => ort.order.account(),
|
||||
ort => ort.order.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
nameof(OrderBookRowVM.account)),
|
||||
@@ -146,9 +146,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Exchange,
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Venue>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, Venue>(
|
||||
EnumHelper.get_values_except(Venue.Unset).ToList(),
|
||||
ort => ort.security.exchange_or_default(),
|
||||
ort => ort.order.security.exchange_or_default(),
|
||||
nameof(OrderBookRowVM.exchange)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.exchange)));
|
||||
@@ -158,14 +158,14 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, TimeInForce>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, TimeInForce>(
|
||||
EnumHelper.get_values<TimeInForce>().ToList(),
|
||||
ort => ort.order.time_in_force(),
|
||||
ort => ort.order.order.time_in_force(),
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
// (algo items)
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key, model.dynamic_helper.dynamic_type_index)).OnDispatcher();
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
|
||||
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
|
||||
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
|
||||
// order type
|
||||
@@ -174,9 +174,9 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.badge_fill),
|
||||
nameof(OrderBookRowVM.display_name),
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityRootPair, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.order.algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
@@ -185,10 +185,10 @@ namespace mbts.spark {
|
||||
Columns.RootAlgoType,
|
||||
nameof(OrderBookRowVM.root_algo_badge_fill),
|
||||
nameof(OrderBookRowVM.root_algo_display_name),
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
_wrap_comparer(r => r.order_tuple.root.algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityRootPair, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root.algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
root_algo_type_items_view.CustomSort = algo_items_comp;
|
||||
@@ -199,36 +199,36 @@ namespace mbts.spark {
|
||||
"Sub Type",
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderSubType>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
ort => ort.order.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
OrderBookGridView.SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Side>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, Side>(
|
||||
new[] { Side.Buy, Side.Sell },
|
||||
ort => ort.order.side(),
|
||||
ort => ort.order.order.side(),
|
||||
nameof(OrderBookRowVM.side))));
|
||||
// quantity
|
||||
Add(qty_column = new SizeColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// edit quantity column
|
||||
// not usually part of the main order book, but it's useful to add it
|
||||
// rather than debugging the popup window
|
||||
//Add(edit_qty_column = new EditableQtyColumn());
|
||||
// filled quantity
|
||||
Add(filled_qty_column = new FilledColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.price(),
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.price(),
|
||||
nameof(OrderBookRowVM.price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.price)));
|
||||
@@ -243,8 +243,8 @@ namespace mbts.spark {
|
||||
"Avg Price",
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.avg_price(),
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.avg_price(),
|
||||
nameof(OrderBookRowVM.avg_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.avg_price)));
|
||||
@@ -254,30 +254,30 @@ namespace mbts.spark {
|
||||
"Last Filled",
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.last_price(),
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_price(),
|
||||
nameof(OrderBookRowVM.last_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.last_price)));
|
||||
// piq
|
||||
Add(piq_column = new PiqColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
// status
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderStatus>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, OrderStatus>(
|
||||
EnumHelper.get_values<OrderStatus>().ToList(),
|
||||
ort => ort.order.status(),
|
||||
ort => ort.order.order.status(),
|
||||
nameof(OrderBookRowVM.status)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.status)));
|
||||
// last message
|
||||
Add(last_message_column = new OrderBookLastMessageColumn(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_msg(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_msg(),
|
||||
nameof(OrderBookRowVM.last_msg))));
|
||||
// security key
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
@@ -285,8 +285,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.security_key().ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.security_key().ToString(),
|
||||
nameof(OrderBookRowVM.security_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.security_key)));
|
||||
@@ -296,8 +296,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.Key.ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.Key.ToString(),
|
||||
nameof(OrderBookRowVM.order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order_key)));
|
||||
@@ -307,8 +307,8 @@ namespace mbts.spark {
|
||||
"Parent Key",
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.ParentKey.ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.ParentKey.ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
@@ -318,8 +318,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order.attached_target_order_key())));
|
||||
@@ -428,9 +428,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public Func<OrderSecurity, bool> get_filter() {
|
||||
public Func<OrderSecurityRootPair, bool> get_filter() {
|
||||
var active_filters = this.OfType<ICVOrderBookColumn>().Where(c => c.filter.active.GetValueOrDefault()).Select(c => c.filter).ToArray();
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurity, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurityRootPair, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
}
|
||||
|
||||
public OrderBookSortingData get_sorting_data() {
|
||||
@@ -531,7 +531,7 @@ namespace mbts.spark {
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
IDataGridColumnPersistentFilter<OrderSecurityRootPair, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
@@ -548,10 +548,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, T> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityRootPair, T> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -565,7 +565,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
IDataGridColumnPersistentFilter<OrderSecurityRootPair, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -575,7 +575,7 @@ namespace mbts.spark {
|
||||
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurity, Side[]> filter)
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -584,7 +584,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -593,7 +593,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -605,7 +605,7 @@ namespace mbts.spark {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurityRootPair, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -615,11 +615,11 @@ namespace mbts.spark {
|
||||
this.comparer = row_comparer;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, AlgoID[]> filter { get; }
|
||||
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -637,7 +637,7 @@ namespace mbts.spark {
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurity, string> filter)
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -646,10 +646,10 @@ namespace mbts.spark {
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, string> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
@@ -659,7 +659,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class PiqColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Piq, "PIQ", r => r.piq_text, DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.piq)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -674,7 +674,7 @@ namespace mbts.spark {
|
||||
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
@@ -683,10 +683,10 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityRootPair, SecurityCriteria> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
@@ -3,9 +3,11 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -14,7 +16,9 @@ using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurity>, INotifyPropertyChanged {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityRootPair>, INotifyPropertyChanged {
|
||||
public static readonly IComparer<OrderBookRowVM> key_comparer = Comparer<uint>.Default.wrap<uint, OrderBookRowVM>(r => r.order_key).to_safe();
|
||||
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -23,13 +27,13 @@ namespace mbts.spark {
|
||||
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
|
||||
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
|
||||
int _depth_level;
|
||||
SecurityImmutable? _security => order_tuple.security;
|
||||
SecurityImmutable? _security => order_tuple.order.security;
|
||||
// for mini-ob
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
Action<bool> _on_selection_changed;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
@@ -45,22 +49,22 @@ namespace mbts.spark {
|
||||
//// when the OrderBookVM.selected_order_changes, this emulates the selected
|
||||
//// order changing since the current_row setter invokes the selected_order_changed
|
||||
//// handler
|
||||
public IObservable<OrderSecurity> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurity>(
|
||||
public IObservable<OrderSecurityRootPair> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityRootPair>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurity oldval, OrderSecurity newval)> updated;
|
||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// Core
|
||||
public OrderSecurity order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order;
|
||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order.order;
|
||||
public OrderMetadata order_metadata => _owner.get_metadata(this);
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.order.meta, order_metadata));
|
||||
public uint order_key => order.Key;
|
||||
public uint parent_order_key => order.ParentKey;
|
||||
public uint security_key => order.security_key();
|
||||
@@ -71,7 +75,7 @@ namespace mbts.spark {
|
||||
public string symbol => _security.symbol_or_default();
|
||||
// Def
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => order.order_type();
|
||||
public string display_name => order_tuple.display_name();
|
||||
public string display_name => order_tuple.order.display_name();
|
||||
public OrderSubType order_sub_type => order.sub_type();
|
||||
public string order_sub_type_text => _fmt_order_sub_type(order_sub_type);
|
||||
public Side side => order.side();
|
||||
@@ -93,7 +97,7 @@ namespace mbts.spark {
|
||||
public uint quantity => order.quantity();
|
||||
public uint filled_quantity => order.filled();
|
||||
public string last_msg => order.last_msg();
|
||||
public uint piq => order_tuple.piq ?? uint.MaxValue;
|
||||
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
|
||||
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
|
||||
public bool is_non_terminal => order.is_non_terminal();
|
||||
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
|
||||
@@ -101,10 +105,10 @@ namespace mbts.spark {
|
||||
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
|
||||
|
||||
// Root Algo
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root_algo.value.CustomCase;
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
|
||||
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
|
||||
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
|
||||
public string root_algo_display_name => order_tuple.root_algo.name;
|
||||
public string root_algo_display_name => order_tuple.root.algo.name;
|
||||
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
@@ -213,7 +217,7 @@ namespace mbts.spark {
|
||||
|
||||
// private props
|
||||
Color _root_algo_badge_color {
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata)).Value;
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root.meta, root_algo_order_metadata)).Value;
|
||||
set {
|
||||
if (_root_algo_badge_color_backing != value) {
|
||||
_root_algo_badge_color_backing = value;
|
||||
@@ -226,7 +230,7 @@ namespace mbts.spark {
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
int IHierarchicalRow.depth_level => depth_level;
|
||||
OrderSecurity IHierarchicalRow<OrderSecurity>.value => order_tuple;
|
||||
OrderSecurityRootPair IHierarchicalRow<OrderSecurityRootPair>.value => order_tuple;
|
||||
|
||||
void IHierarchicalRow.on_is_parent_changed(bool is_parent) => this.is_parent = is_parent;
|
||||
/// Do NOT actually call the <see cref="is_expanded"/> setter (it has side effects) - the row collection calls this method when the row is expanded/collapsed by some **external means, outside of this class.
|
||||
@@ -238,7 +242,7 @@ namespace mbts.spark {
|
||||
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_row(TKey)"/>
|
||||
void IHierarchicalRow.on_is_expanded_changed(bool is_expanded) => Set(ref _is_expanded, is_expanded, nameof(is_expanded));
|
||||
void IHierarchicalRow.on_depth_level_changed(int depth_level) => this.depth_level = depth_level;
|
||||
void IHierarchicalRow<OrderSecurity>.on_value_changed(OrderSecurity value) => update(value);
|
||||
void IHierarchicalRow<OrderSecurityRootPair>.on_value_changed(OrderSecurityRootPair value) => update(value);
|
||||
#endregion
|
||||
|
||||
private void _unselect_me() {
|
||||
@@ -254,8 +258,8 @@ namespace mbts.spark {
|
||||
internal void expand_internal() => Set(ref _is_expanded, true, nameof(is_expanded));
|
||||
internal void collapse_internal() => Set(ref _is_expanded, false, nameof(is_expanded));
|
||||
|
||||
public void update(OrderSecurity order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
public void update(OrderSecurityRootPair order_tuple) {
|
||||
Log.assert(order_tuple.order.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
|
||||
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
|
||||
var old_order_tuple = this.order_tuple;
|
||||
@@ -281,25 +285,25 @@ namespace mbts.spark {
|
||||
if ((Columns.Security & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(security), nameof(security_text));
|
||||
// badge fill
|
||||
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
|
||||
// invalidate by setting to null (will be re-instantiated in the public getter)
|
||||
_badge_fill = null;
|
||||
RaisePropertyChanged(nameof(badge_fill));
|
||||
}
|
||||
// display name
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
if (old_order_tuple.order.algo.name != order_tuple.order.algo.name)
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
// root badge fill
|
||||
if (_root_algo_badge_color_backing.HasValue) {
|
||||
// only need to invalidate it if it had a value (it is lazily instantiated)
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata);
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root.meta, root_algo_order_metadata);
|
||||
RaisePropertyChanged(nameof(root_algo_badge_fill));
|
||||
}
|
||||
// root display name
|
||||
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
|
||||
if (old_order_tuple.root.algo.name != order_tuple.root.algo.name)
|
||||
RaisePropertyChanged(nameof(root_algo_display_name));
|
||||
// root order type
|
||||
if (old_order_tuple.root_algo.value.CustomCase != order_tuple.root_algo.value.CustomCase) {
|
||||
if (old_order_tuple.root.algo.value.CustomCase != order_tuple.root.algo.value.CustomCase) {
|
||||
RaisePropertyChanged(nameof(root_algo_order_type));
|
||||
}
|
||||
// status
|
||||
@@ -317,7 +321,7 @@ namespace mbts.spark {
|
||||
// always raise updated
|
||||
updated?.Invoke((old_order_tuple, order_tuple));
|
||||
// terminal state
|
||||
if (old_order_tuple.order.is_non_terminal() != order_tuple.order.is_non_terminal()) {
|
||||
if (old_order_tuple.order.order.is_non_terminal() != order_tuple.order.order.is_non_terminal()) {
|
||||
// while we call refresh_is_at/detachable in the OrderBookRowCollection.update
|
||||
// on all the children of a modified row, we used to not call it on the row istelf,
|
||||
// which should of course be done here. This manifested via the very top parent in any
|
||||
@@ -398,35 +402,35 @@ namespace mbts.spark {
|
||||
Columns.AvgFilled |
|
||||
Columns.Status;
|
||||
|
||||
public static Columns get_dirty_cells(OrderSecurity oldval, OrderSecurity newval) {
|
||||
public static Columns get_dirty_cells(OrderSecurityRootPair oldval, OrderSecurityRootPair newval) {
|
||||
var dirty = (Columns)0;
|
||||
if (oldval.security != newval.security)
|
||||
if (oldval.order.security != newval.order.security)
|
||||
dirty |= Columns.Security;
|
||||
if (oldval.order.last_change() != newval.order.last_change())
|
||||
if (oldval.order.order.last_change() != newval.order.order.last_change())
|
||||
dirty |= Columns.LastChange;
|
||||
if (oldval.order.filled() != newval.order.filled())
|
||||
if (oldval.order.order.filled() != newval.order.order.filled())
|
||||
dirty |= Columns.Filled;
|
||||
if (oldval.order.last_price() != newval.order.last_price())
|
||||
if (oldval.order.order.last_price() != newval.order.order.last_price())
|
||||
dirty |= Columns.LastPrice;
|
||||
if (oldval.order.avg_price() != newval.order.avg_price())
|
||||
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
|
||||
dirty |= Columns.AvgFilled;
|
||||
if (oldval.order.price() != newval.order.price())
|
||||
if (oldval.order.order.price() != newval.order.order.price())
|
||||
dirty |= Columns.Price;
|
||||
if (oldval.order.quantity() != newval.order.quantity())
|
||||
if (oldval.order.order.quantity() != newval.order.order.quantity())
|
||||
dirty |= Columns.Size;
|
||||
if (oldval.order.ParentKey != newval.order.ParentKey)
|
||||
if (oldval.order.order.ParentKey != newval.order.order.ParentKey)
|
||||
dirty |= Columns.ParentKey;
|
||||
if (oldval.order.attached_target_order_key() != newval.order.attached_target_order_key())
|
||||
if (oldval.order.order.attached_target_order_key() != newval.order.order.attached_target_order_key())
|
||||
dirty |= Columns.AttachedTargetOrderKey;
|
||||
if (oldval.order.status() != newval.order.status())
|
||||
if (oldval.order.order.status() != newval.order.order.status())
|
||||
dirty |= Columns.Status;
|
||||
if (oldval.order.last_msg() != newval.order.last_msg())
|
||||
if (oldval.order.order.last_msg() != newval.order.order.last_msg())
|
||||
dirty |= Columns.LastMessage;
|
||||
if (oldval.piq != newval.piq) {
|
||||
if (oldval.order.piq != newval.order.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,9 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly ObservableSortedCollection<OrderBookRowVM> _selected_rows = new ObservableSortedCollection<OrderBookRowVM>(OrderBookRowVM.key_comparer);
|
||||
readonly DelegateProperty<bool> _show_order_params, _show_ladder, _show_fills, _can_attach_selected_orders, _can_detach_selected_orders;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityRootPair, uint> _row_collection;
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
@@ -61,13 +62,12 @@ namespace mbts.spark {
|
||||
_is_attaching_orders,
|
||||
_is_row_collection_populated,
|
||||
_all_rows_selected;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
IReadOnlyDictionary<uint, OrderSecurityRootPair> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityRootPair>();
|
||||
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
|
||||
HashSet<uint>
|
||||
_attachable_selected_orders = new HashSet<uint>(),
|
||||
_detachable_selected_orders = new HashSet<uint>();
|
||||
FilterData _cached_filter_data;
|
||||
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
|
||||
|
||||
public OrderBookVM(
|
||||
IBus bus,
|
||||
@@ -78,9 +78,9 @@ namespace mbts.spark {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle, k => _cached_orders.TryGetValue(k, out var o) ? o.order : (OrderSecurityAlgo?)null);
|
||||
ladder_vm = new LadderVMContainer(bus, model);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoFormats.Exchange, AlgoEditingPermissions.AllowEditValues);
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
_show_order_params = backing(nameof(show_order_params), () => (_right_panel.visible_components & OrderBookRightPanelSettings.Components.Params) != 0);
|
||||
@@ -93,16 +93,12 @@ namespace mbts.spark {
|
||||
};
|
||||
_can_attach_selected_orders = backing(nameof(can_attach_selected_orders), () => _attachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
|
||||
_can_detach_selected_orders = backing(nameof(can_detach_selected_orders), () => _detachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
|
||||
attach_vm = new OrderParamsVM(this,
|
||||
AlgoDefinitionVM.create(_model,
|
||||
OrderDataHelper.Defaults.AlgoData(CustomOrderDefinition.CustomOneofCase.Iceberg),
|
||||
init_format: AlgoFormats.Attachable,
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues));
|
||||
attach_vm = new OrderParamsVM(this, AlgoFormats.Attachable, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
|
||||
_settings_manager = settings_manager;
|
||||
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
|
||||
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
|
||||
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityRootPair, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded, (row_is_selected) => {
|
||||
// all are selected and one is checked off
|
||||
if (all_rows_selected && !row_is_selected) {
|
||||
@@ -114,11 +110,11 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(this.all_rows_selected));
|
||||
}
|
||||
}) {
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.order.Key, out bool id)
|
||||
},
|
||||
r => r.order.Key,
|
||||
r => r.order.ParentKey,
|
||||
r => r.order.order.Key,
|
||||
r => r.order.order.ParentKey,
|
||||
_columns.get_sorting_data().comparer);
|
||||
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
@@ -189,8 +185,24 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(security_plurality));
|
||||
}),
|
||||
orders_source.OnDispatcher().Subscribe(t => {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
|
||||
if (_selected_rows.Count == 0) {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(OrderSecurityHelper.root_pair_key_fn));
|
||||
} else {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(OrderSecurityHelper.root_pair_key_fn));
|
||||
// Remove, then re-add, all selected roots from _selected_params_mon
|
||||
// to capture algo param changes at any level (this keeps the update panel in sync).
|
||||
_selected_params_mon.Clear();
|
||||
_selected_security_mon.clear();
|
||||
foreach (var r in _selected_rows) {
|
||||
_selected_params_mon.Add(r.order_tuple.root);
|
||||
_selected_security_mon.add(r.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
|
||||
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
|
||||
@@ -271,11 +283,11 @@ namespace mbts.spark {
|
||||
try {
|
||||
double price = attach_vm.price;
|
||||
uint? size = attach_vm.size;
|
||||
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted.id_uint : 0u;
|
||||
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted : 0u;
|
||||
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
|
||||
var mod_algo_vm = attach_vm.algo_vm.algo_root;
|
||||
await Task.WhenAll(order_tups.Select(t => {
|
||||
var algo = t.order.Def.CustomDef.actual().CloneToMutable();
|
||||
var algo = t.order.order.Def.CustomDef.actual().CloneToMutable();
|
||||
mod_algo_vm.assign_to(algo);
|
||||
if (algo_template_key != 0) {
|
||||
algo = new CustomOrderDefinition {
|
||||
@@ -286,12 +298,12 @@ namespace mbts.spark {
|
||||
};
|
||||
}
|
||||
return _model.send_order(
|
||||
t.order.Def.with(
|
||||
attach_target_order_key: t.order.Key,
|
||||
t.order.order.Def.with(
|
||||
attach_target_order_key: t.order.order.Key,
|
||||
custom_def: algo
|
||||
),
|
||||
double.IsNaN(price) ? t.order.price() : price,
|
||||
size ?? t.order.quantity());
|
||||
double.IsNaN(price) ? t.order.order.price() : price,
|
||||
size ?? t.order.order.quantity());
|
||||
}));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error sending attached orders: {ex}.");
|
||||
@@ -318,14 +330,13 @@ namespace mbts.spark {
|
||||
}),
|
||||
// custom templates (created by user)
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_factory(true)))));
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_algo_def_vm))));
|
||||
// vm factory
|
||||
Func<AlgoData, AlgoDefinitionVM> mk_factory(bool connect) =>
|
||||
fd => {
|
||||
var vm = AlgoDefinitionVM.create(_model, fd, connect, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
|
||||
return vm;
|
||||
};
|
||||
AlgoDefinitionVM mk_algo_def_vm(AlgoData fd) {
|
||||
var vm = AlgoDefinitionVM.create(_model, fd, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
|
||||
|
||||
@@ -344,6 +355,7 @@ namespace mbts.spark {
|
||||
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
|
||||
|
||||
}
|
||||
|
||||
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -375,7 +387,7 @@ namespace mbts.spark {
|
||||
public OrderParamsVM order_mod_vm { get; }
|
||||
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
|
||||
public OrderParamsVM attach_vm { get; }
|
||||
public ObservableCollection<OrderBookRowVM> selected_rows { get; } = new ObservableCollection<OrderBookRowVM>();
|
||||
public IReadOnlyObservableCollection<OrderBookRowVM> selected_rows => _selected_rows;
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => selected_rows.Select(r => r.order);
|
||||
public IEnumerable<OrderBookRowVM> selected_hierarchical_rows => selected_rows.SelectMany(r => new[] { r }.Concat(_row_collection.get_descendent_rows(r.order_key)));
|
||||
public OrderBookGridSettingsManager grid_settings_manager { get; }
|
||||
@@ -456,65 +468,30 @@ namespace mbts.spark {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
if (e.RemovedItems != null) {
|
||||
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = false;
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
foreach (var row in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row.is_selected = false;
|
||||
_selected_rows.Remove(row);
|
||||
_selected_params_mon.Remove(row.order_tuple.root);
|
||||
_selected_security_mon.remove(row.security);
|
||||
_attachable_selected_orders.Remove(row.order_key);
|
||||
_detachable_selected_orders.Remove(row.order_key);
|
||||
}
|
||||
}
|
||||
if (e.AddedItems != null) {
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
var root = _get_root(row_vm.order_tuple);
|
||||
row_vm.is_selected = true;
|
||||
_selected_security_mon.add(row_vm.security);
|
||||
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
|
||||
row_vm.updated += on_updated;
|
||||
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
|
||||
}
|
||||
_selected_params_mon.Add(root);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
_detachable_selected_orders.Add(row_vm.order_key);
|
||||
void on_updated((OrderSecurity oldval, OrderSecurity newval) change) {
|
||||
if (!row_vm.is_selected) {
|
||||
// self destruct
|
||||
if (_on_updated_single_callback_gatekeeper.TryGetValue(row_vm.order_key, out var unsubscribe_updated_disposable)) {
|
||||
unsubscribe_updated_disposable.Dispose();
|
||||
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
|
||||
}
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
return;
|
||||
}
|
||||
var new_root = _get_root(change.newval);
|
||||
if (!new_root.Equals(root)) {
|
||||
using (_selected_params_mon.defer_updates()) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.Remove(root);
|
||||
_selected_params_mon.Add(new_root);
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
if (change.oldval.security?.Id != change.newval.security?.Id) {
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_selected_security_mon.remove(change.oldval.security);
|
||||
_selected_security_mon.add(change.newval.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var row in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
row.is_selected = true;
|
||||
_selected_rows.Add(row);
|
||||
_selected_params_mon.Add(row.order_tuple.root);
|
||||
_selected_security_mon.add(row.security);
|
||||
if (row.is_attachable) _attachable_selected_orders.Add(row.order_key);
|
||||
if (row.is_detachable) _detachable_selected_orders.Add(row.order_key);
|
||||
}
|
||||
}
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
} catch (Exception ex) {
|
||||
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
|
||||
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
|
||||
}
|
||||
@@ -584,11 +561,11 @@ namespace mbts.spark {
|
||||
if (!new_sort.Equals(old_sort)) {
|
||||
/// Both filter and sort have changed, use <see cref="OperableHierarchicalCollection{TRow, T, TKey}.defer_refresh"/> to avoid syncing twice.
|
||||
using (_row_collection.defer_refresh()) {
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
}
|
||||
} else {
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
}
|
||||
} else if (!new_sort.Equals(old_sort))
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
@@ -648,17 +625,11 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
void _on_filter_changed() {
|
||||
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
|
||||
if (!fd.Equals(_cached_filter_data)) {
|
||||
_cached_filter_data = fd;
|
||||
_row_collection.filter = fd.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
_row_collection.filter = fd.build_predicate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,9 +658,9 @@ namespace mbts.spark {
|
||||
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
|
||||
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
|
||||
// ----
|
||||
var update_params = selected_rows.Select(r => update_params_factory(_get_root(r.order_tuple))).ToList();
|
||||
var update_params = selected_rows.Select(r => update_params_factory(r.order_tuple.root)).ToList();
|
||||
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurity order_tuple) {
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurityAlgo order_tuple) {
|
||||
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
|
||||
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
|
||||
@@ -702,7 +673,7 @@ namespace mbts.spark {
|
||||
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
|
||||
}
|
||||
|
||||
IObservable<ObservableCollection<OrderBookRowVM>> _subscribe_throttled_selected_rows() =>
|
||||
IObservable<IReadOnlyList<OrderBookRowVM>> _subscribe_throttled_selected_rows() =>
|
||||
selected_rows.Observe().BufferLatest(TimeSpan.FromSeconds(0.2)).Select(_ => selected_rows);
|
||||
|
||||
static OrderBookFillsGridSettings _get_fills_settings(FillsVM fills_vm) =>
|
||||
@@ -752,14 +723,15 @@ namespace mbts.spark {
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner)
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
new AlgoData(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg)),
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditValues)) { }
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions)) { }
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
_owner = owner;
|
||||
this.algo_vm = algo_vm;
|
||||
_accounts = backing(
|
||||
@@ -904,8 +876,8 @@ namespace mbts.spark {
|
||||
this.column_filters = column_filters;
|
||||
}
|
||||
|
||||
public Func<OrderSecurity, bool> build_predicate(IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pred = column_filters.build_predicate(dynamic_type_index);
|
||||
public Func<OrderSecurityRootPair, bool> build_predicate() {
|
||||
var pred = column_filters.build_predicate();
|
||||
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
|
||||
if (pred == null) {
|
||||
pred = tree_disp_pred;
|
||||
@@ -913,10 +885,10 @@ namespace mbts.spark {
|
||||
var col_pred = pred;
|
||||
pred = o => tree_disp_pred(o) && col_pred(o);
|
||||
}
|
||||
bool tree_disp_pred(OrderSecurity o) =>
|
||||
!o.order.has_parent() ||
|
||||
o.order.is_exchange_order() ||
|
||||
o.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
bool tree_disp_pred(OrderSecurityRootPair o) =>
|
||||
!o.order.order.has_parent() ||
|
||||
o.order.order.is_exchange_order() ||
|
||||
o.order.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
}
|
||||
return pred;
|
||||
}
|
||||
|
||||
@@ -119,36 +119,36 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
public static Func<OrderSecurityRootPair, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurityRootPair>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key, dynamic_type_index));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.price(), o => o.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.order.price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,29 +71,19 @@ namespace mbts.spark.utilities {
|
||||
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
|
||||
|
||||
#region Defaults
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type() =>
|
||||
CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child = false) {
|
||||
if (is_child) return CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security) {
|
||||
switch (security.ContractCase) {
|
||||
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
default: return CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
}
|
||||
}
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_order_type(security.Value, is_child) : default_order_type();
|
||||
|
||||
public TimeInForce default_time_in_force() =>
|
||||
TimeInForce.Day;
|
||||
|
||||
public TimeInForce default_time_in_force(SecurityImmutable security) =>
|
||||
default_time_in_force();
|
||||
|
||||
public TimeInForce default_time_in_force(SecurityImmutable? security) =>
|
||||
security.HasValue ? default_time_in_force(security.Value) : default_time_in_force();
|
||||
|
||||
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types() =>
|
||||
OrderDataHelper.exchange_order_types;
|
||||
|
||||
@@ -145,15 +135,15 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public Dictionary<AlgoKey, AlgoData> default_algos(IEnumerable<CustomOrderDefinition.CustomOneofCase> types) {
|
||||
var def_map = new Dictionary<AlgoKey, AlgoData>();
|
||||
foreach (var ot in types.Except(new[] { CustomOrderDefinition.CustomOneofCase.Dynamic })) {
|
||||
var key = new AlgoKey(new AlgoNameStructureKey(ot));
|
||||
def_map[key] = new AlgoData(custom_orderi(ot), ot.display_name(), key);
|
||||
foreach (var ot in types.Except(new[] { CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite })) {
|
||||
var data = AlgoData.from_custom_def(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(ot));
|
||||
def_map[data.key] = data;
|
||||
}
|
||||
var dynamic_defs = new List<CustomOrderDefinitionImmutable>();
|
||||
_dynamic_helper.add_dynamic_order_defs(dynamic_defs, custom_order);
|
||||
foreach (var dd in dynamic_defs) {
|
||||
var key = new AlgoKey(new AlgoNameStructureKey(dd.Dynamic.DynamicOrderType, new AlgoStructureKey(CustomOrderDefinition.CustomOneofCase.Dynamic)));
|
||||
def_map[key] = new AlgoData(dd, dd.Dynamic.DynamicOrderType, key);
|
||||
var data = AlgoData.from_custom_def(dd);
|
||||
def_map[data.key] = data;
|
||||
}
|
||||
return def_map;
|
||||
}
|
||||
@@ -161,6 +151,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public OrderDefinitionImmutable order(SecurityImmutable security) =>
|
||||
new OrderDefinitionImmutable(order(security, _admin_model.get_default_account()));
|
||||
|
||||
public OrderDefinition order(SecurityImmutable security, AccountTuple? account) {
|
||||
var rv = new OrderDefinition {
|
||||
SecurityKey = security.Id,
|
||||
@@ -313,6 +304,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public static OneofDescriptor oneof_descriptor => _oneof_desc;
|
||||
|
||||
const string _primary_child_field = "child";
|
||||
// algo-type-case maps
|
||||
// Note: obv this will break if we change the name of the 'CustomOneofCase' field
|
||||
static readonly OneofDescriptor _oneof_desc = CustomOrderDefinition.Descriptor.Oneofs.FirstOrDefault(fd => fd.Name == "custom");
|
||||
@@ -356,35 +348,69 @@ namespace mbts.spark.utilities {
|
||||
f.FieldType == FieldType.Message && f.MessageType.ClrType == typeof(CustomOrderDefinition);
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_children(CustomOrderDefinition algo) {
|
||||
switch (algo.CustomCase) {
|
||||
#region Algo Structure/Hierarchy
|
||||
public static IEnumerable<(CustomOrderDefinition.CustomOneofCase type, string dyn, uint studio, uint spreader_sec)> enumerate_algo_structure(CustomOrderDefinitionImmutable custom_defi) {
|
||||
string dyn = null;
|
||||
uint studio = 0, spreader_sec = 0;
|
||||
foreach (var cd in enumerate_algo_hierarchy(custom_defi.CloneToMutable())) {
|
||||
switch (cd.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: studio = cd.Composite.AlgoTemplateKey; break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: dyn = cd.Dynamic.DynamicOrderType; break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: spreader_sec = cd.Spreader.Security.Id; break;
|
||||
}
|
||||
yield return (cd.CustomCase, dyn, studio, spreader_sec);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates through every algo member immediately defined <paramref name="custom_def"/> (i.e., no recursion, just the first level of children).
|
||||
/// </summary>
|
||||
/// <param name="custom_defi">The root algo, from which enumeration begins.</param>
|
||||
/// <returns>An IEnumerable of each algo member contained in <paramref name="custom_def"/></returns>
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_children(CustomOrderDefinition custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
foreach (var child in enumerate_dynamic_algo_children(algo.Dynamic))
|
||||
yield return child;
|
||||
foreach (var kv in custom_def.Dynamic.OrderParams) {
|
||||
if (kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
yield return kv.Value.AsOrderDef ?? new CustomOrderDefinition();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
foreach (var cad in get_child_algo_data(algo)) {
|
||||
if (cad.get_child(algo) is CustomOrderDefinition child)
|
||||
yield return child;
|
||||
var child_algo_data = get_child_algo_data(custom_def);
|
||||
if (child_algo_data.Count > 0) {
|
||||
// need to convert to underlying proto type to use the [reflection-based]generated child selectors
|
||||
for (int i = 0; i < child_algo_data.Count; i++) {
|
||||
// don't skip undefined children (this normalizes the structure).
|
||||
// each algo type should always yield the same number of children.
|
||||
yield return child_algo_data[i].get_child(custom_def) ?? new CustomOrderDefinition();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_dynamic_algo_children(DynamicOrderDefinition dynamic_algo) {
|
||||
foreach (var kv in dynamic_algo.OrderParams) {
|
||||
if (kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
yield return kv.Value.AsOrderDef;
|
||||
/// <summary>
|
||||
/// Enumerates through every algo member contained in <paramref name="custom_def"/>, excluding <paramref name="custom_def"/> itself.
|
||||
/// </summary>
|
||||
/// <param name="custom_defi">The root algo, from which enumeration begins.</param>
|
||||
/// <returns>An IEnumerable of each algo member contained in <paramref name="custom_def"/></returns>
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_descendents(CustomOrderDefinition custom_def) {
|
||||
foreach (var child in enumerate_algo_children(custom_def)) {
|
||||
yield return child;
|
||||
foreach (var grand_child in enumerate_algo_descendents(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_hierarchy(CustomOrderDefinition algo) {
|
||||
yield return algo;
|
||||
foreach (var child in enumerate_algo_children(algo)) {
|
||||
yield return child;
|
||||
foreach (var grand_child in enumerate_algo_hierarchy(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
/// <summary>
|
||||
/// Enumerates through every algo member contained in <paramref name="custom_def"/>, including <paramref name="custom_def"/> itself.
|
||||
/// </summary>
|
||||
/// <param name="custom_def">The root algo, from which enumeration begins.</param>
|
||||
/// <returns>An IEnumerable of each algo member contained in <paramref name="custom_def"/></returns>
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_hierarchy(CustomOrderDefinition custom_def) {
|
||||
yield return custom_def;
|
||||
foreach (var d in enumerate_algo_descendents(custom_def))
|
||||
yield return d;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinitionImmutable defi) =>
|
||||
@@ -412,6 +438,12 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public static ChildAlgoData? get_primary_child_algo_data(CustomOrderDefinition algo) {
|
||||
var child_data = get_child_algo_data(algo);
|
||||
return child_data.TryGetFirst(cad => cad.name == _primary_child_field, out ChildAlgoData best_match) ? best_match :
|
||||
child_data.TryGetFirst(out best_match) ? best_match : (ChildAlgoData?)null;
|
||||
}
|
||||
|
||||
public static CustomOrderDefinition create_algo(CustomOrderDefinition.CustomOneofCase algo_type) {
|
||||
var algo = new CustomOrderDefinition();
|
||||
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData desc))
|
||||
@@ -419,8 +451,18 @@ namespace mbts.spark.utilities {
|
||||
return algo;
|
||||
}
|
||||
|
||||
public static bool append_algo_node(CustomOrderDefinition target, CustomOrderDefinition.CustomOneofCase algo_type) {
|
||||
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out var desc)) {
|
||||
var node = ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor));
|
||||
desc.type_case_descriptor.Accessor.SetValue(target, node);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition.CustomOneofCase algo_type) =>
|
||||
_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData d) ? d.child_algo_data : Array.Empty<ChildAlgoData>();
|
||||
#endregion
|
||||
|
||||
public static OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) {
|
||||
switch (order_type) {
|
||||
@@ -466,6 +508,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return "TG";
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: return "JN";
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return "SR";
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return "COMP";
|
||||
default: return algo_type.ToString();
|
||||
}
|
||||
}
|
||||
@@ -503,7 +546,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public static IReadOnlyDictionary<AlgoKey, AlgoData> get_format_algo_data(AlgoFormats f) {
|
||||
var algo_types = get_format_algo_types(f);
|
||||
var algo_dict = algo_types.Select(at => new AlgoData(Defaults.CustomOrderDefinitionImmutable(at), at.display_name(), new AlgoKey(at))).ToDictionaryCrushed(a => a.key);
|
||||
var algo_dict = algo_types.Select(at => AlgoData.from_custom_def(Defaults.CustomOrderDefinitionImmutable(at))).ToDictionaryCrushed(a => a.key);
|
||||
return algo_dict;
|
||||
}
|
||||
|
||||
@@ -588,63 +631,25 @@ namespace mbts.spark.utilities {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a key that is uniquely identified by <see cref="CustomOrderDefinition.CustomOneofCase"/>,
|
||||
/// and potentially <see cref="DynamicOrderDefinition.DynamicOrderType"/> (if it is a <see cref="DynamicOrderDefinition"/>).
|
||||
/// </summary>
|
||||
public static AlgoKey get_shallow_factory_key(CustomOrderDefinitionImmutable defi) =>
|
||||
new AlgoKey(new AlgoNameStructureKey(
|
||||
defi.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? defi.Dynamic.DynamicOrderType : defi.CustomCase.type_id(),
|
||||
new AlgoStructureKey(defi.CustomCase)));
|
||||
|
||||
static CustomOrderDefinition.CustomOneofCase[] algo_config(
|
||||
Func<bts.config.AlgoPermissions, List<string>> selector)
|
||||
{
|
||||
var conf = mbts.config.Configuration.get<bts.config.AlgoPermissions>();
|
||||
return selector(conf).Select(x =>
|
||||
{
|
||||
return (CustomOrderDefinition.CustomOneofCase)System.Enum.Parse(typeof(CustomOrderDefinition.CustomOneofCase), x, true);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[]
|
||||
|
||||
// exchange_order_types are the types which can be submitted on an exchange security
|
||||
exchange_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset,
|
||||
CustomOrderDefinition.CustomOneofCase.Splitter,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
spreader_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Spreader,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Splitter,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
attachable_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
outright_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
};
|
||||
exchange_order_types = algo_config(x => x.exchange_order_types),
|
||||
spreader_order_types = algo_config(x => x.spreader_order_types),
|
||||
attachable_order_types = algo_config(x => x.attachable_order_types),
|
||||
outright_order_types = algo_config(x => x.outright_order_types);
|
||||
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
|
||||
|
||||
public static void copy_values(CustomOrderDefinitionImmutable source, ref CustomOrderDefinition target) =>
|
||||
@@ -702,17 +707,11 @@ namespace mbts.spark.utilities {
|
||||
// That causes problems in some usages (e.g., AlgoStudio, and the Ladder/Ticket algo popup), bc double.NaN != double.NaN.
|
||||
// These equality comparers should be user in such cases, as they use the actual double.Equals method, not the == operator.
|
||||
// https://github.com/protocolbuffers/protobuf/issues/3725
|
||||
public static readonly IEqualityComparer<CustomOrderDefinitionImmutable> custom_order_def_compareri;
|
||||
public static readonly IEqualityComparer<CompositeAlgoDefinitionImmutable> algo_def_compareri;
|
||||
public static readonly IEqualityComparer<CustomOrderDefinitionImmutable>
|
||||
custom_order_defi_comparer = new CustomDefinitionComparer(),
|
||||
custom_order_defi_structure_comparer = new CustomDefinitionStructureComparer();
|
||||
|
||||
static EqualsHelpers() {
|
||||
int mod = MathUtils.Primes.P_1087;
|
||||
custom_order_def_compareri = new _Compareri(mod);
|
||||
algo_def_compareri = new CompositeAlgoDefinitionImmutableComparer(mod, custom_order_def_compareri);
|
||||
}
|
||||
|
||||
public static bool equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) => custom_order_def_compareri.Equals(a, b);
|
||||
public static bool equals(CompositeAlgoDefinitionImmutable a, CompositeAlgoDefinitionImmutable b) => algo_def_compareri.Equals(a, b);
|
||||
public static bool equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) => custom_order_defi_comparer.Equals(a, b);
|
||||
|
||||
public static bool structure_equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
|
||||
return impl(a.CloneToMutable(), b.CloneToMutable());
|
||||
@@ -857,11 +856,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
class _Compareri : IEqualityComparer<CustomOrderDefinitionImmutable> {
|
||||
readonly int _mod;
|
||||
|
||||
public _Compareri(int mod) => _mod = mod;
|
||||
|
||||
class CustomDefinitionComparer : IEqualityComparer<CustomOrderDefinitionImmutable> {
|
||||
public bool Equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
|
||||
if (a.CustomCase != b.CustomCase) return false;
|
||||
switch (a.CustomCase) {
|
||||
@@ -945,29 +940,17 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
|
||||
void _add_component(ref int hash, ref int phase_mult, int component_hash) =>
|
||||
hash += EqualityComparer.phase_hash(component_hash, phase_mult++, _mod);
|
||||
hash += EqualityComparer.phase_hash(component_hash, phase_mult++, MathUtils.Primes.P_1087);
|
||||
|
||||
int _get_hash(CustomOrderDefinitionImmutable? a) => a.HasValue ? GetHashCode(a.Value) : 0;
|
||||
}
|
||||
|
||||
class CompositeAlgoDefinitionImmutableComparer : IEqualityComparer<CompositeAlgoDefinitionImmutable> {
|
||||
readonly int _mod;
|
||||
readonly IEqualityComparer<CustomOrderDefinitionImmutable> _def_comparer;
|
||||
class CustomDefinitionStructureComparer : IEqualityComparer<CustomOrderDefinitionImmutable> {
|
||||
public bool Equals(CustomOrderDefinitionImmutable def1, CustomOrderDefinitionImmutable def2) =>
|
||||
enumerate_algo_structure(def1).SequenceEqual(enumerate_algo_structure(def2));
|
||||
|
||||
public CompositeAlgoDefinitionImmutableComparer(int mod, IEqualityComparer<CustomOrderDefinitionImmutable> def_comparer) {
|
||||
_mod = mod;
|
||||
_def_comparer = def_comparer;
|
||||
}
|
||||
|
||||
public bool Equals(CompositeAlgoDefinitionImmutable x, CompositeAlgoDefinitionImmutable y) =>
|
||||
x.Name == y.Name &&
|
||||
x.HasAlgo ?
|
||||
y.HasAlgo && _def_comparer.Equals(x.Algo, y.Algo) :
|
||||
!y.HasAlgo;
|
||||
|
||||
public int GetHashCode(CompositeAlgoDefinitionImmutable algo_def) =>
|
||||
EqualityComparer.safe_get_hash_code(algo_def.Name) % _mod +
|
||||
EqualityComparer.phase_hash(algo_def.HasAlgo ? _def_comparer.GetHashCode(algo_def.Algo) : 0, 1, _mod);
|
||||
public int GetHashCode(CustomOrderDefinitionImmutable def) =>
|
||||
EqualityComparer.auto_phase(enumerate_algo_structure(def));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,8 +969,8 @@ namespace mbts.spark.utilities {
|
||||
// param types
|
||||
public static QuantityMeasure QuantityMeasure => new QuantityMeasure { Absolute = 1 };
|
||||
// core data types
|
||||
public static AlgoData AlgoData() => new AlgoData(CustomOrderDefinitionImmutable());
|
||||
public static AlgoData AlgoData(CustomOrderDefinition.CustomOneofCase algo_type) => new AlgoData(CustomOrderDefinitionImmutable(algo_type));
|
||||
public static AlgoData AlgoData() => spark.AlgoData.from_custom_def(CustomOrderDefinitionImmutable());
|
||||
public static AlgoData AlgoData(CustomOrderDefinition.CustomOneofCase algo_type) => spark.AlgoData.from_custom_def(CustomOrderDefinitionImmutable(algo_type));
|
||||
|
||||
public static OrderDefinition OrderDefinition => new OrderDefinition { CustomDef = new CustomOrderDefinition() };
|
||||
public static OrderRecord OrderRecord => new OrderRecord {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -16,24 +14,26 @@ namespace mbts.spark {
|
||||
/// </summary>
|
||||
class OrderParamsAggregator {
|
||||
readonly TimeSpan _throttle;
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
|
||||
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
|
||||
|
||||
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle) {
|
||||
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_throttle = throttle;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
|
||||
_lookup_order = lookup_order;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
|
||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark {
|
||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _all_active_mon.common_value;
|
||||
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals, Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
@@ -61,45 +61,75 @@ namespace mbts.spark {
|
||||
public IDisposable defer_updates() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
event Action<TableUpdate<OrderSecurity, uint>> _updated;
|
||||
public void Update(TableUpdate<OrderSecurity, uint> table) {
|
||||
_updated?.Invoke(table);
|
||||
}
|
||||
|
||||
public void Add(OrderSecurity order_tuple) =>
|
||||
public void Add(OrderSecurityAlgo order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void Remove(OrderSecurity order_tuple) =>
|
||||
public void Remove(OrderSecurityAlgo order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
public void Clear() =>
|
||||
_group_mon.clear();
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
|
||||
root.order.Def.AttachTargetOrderKey == 0 ?
|
||||
root.order.Def.CustomDef :
|
||||
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
|
||||
var lin = _lineage(root_id, attached_target);
|
||||
if (lin != null && lin.Count > 0) {
|
||||
var mparent = mroot;
|
||||
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
|
||||
if (pcad.HasValue) {
|
||||
do {
|
||||
var mchild = lin.Pop().order.Def.CustomDef.CloneToMutable();
|
||||
pcad.Value.assign_child(mparent, mchild);
|
||||
mparent = mchild;
|
||||
pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
|
||||
} while (lin.Count > 0 && pcad.HasValue);
|
||||
}
|
||||
}
|
||||
return mroot;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
|
||||
// check args/preconditions
|
||||
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
|
||||
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
|
||||
if (target == root) return null;
|
||||
var inner = _lookup_order(target);
|
||||
if (!inner.HasValue)
|
||||
return null;
|
||||
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
|
||||
// then push the original target onto the stack, and try to hop up each algo-generation
|
||||
// via parent-key lookups until we reach the root.
|
||||
// **If the lineage is disjointed, and we never reach the root, return null**
|
||||
var ival = inner.Value;
|
||||
var lin_stack = ival.order.attached_target_order_key() != 0 ?
|
||||
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
|
||||
new Stack<OrderSecurityAlgo>();
|
||||
lin_stack.Push(ival);
|
||||
target = ival.order.ParentKey;
|
||||
while (target != root) {
|
||||
if (target == 0) {
|
||||
// **disjointed lineage**
|
||||
return null;
|
||||
}
|
||||
inner = _lookup_order(target);
|
||||
if (!inner.HasValue) {
|
||||
// **disjointed lineage**
|
||||
return null;
|
||||
}
|
||||
ival = inner.Value;
|
||||
lin_stack.Push(ival);
|
||||
target = ival.order.ParentKey;
|
||||
}
|
||||
return lin_stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,6 @@ namespace mbts.spark {
|
||||
class OrderPropertiesEditorVM : DisposableViewModelBase {
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<SecurityImmutable> _security;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_algo_template, _selected_favorite_algo_template;
|
||||
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
|
||||
readonly SelectorProperty<TimeInForce> _time_in_force;
|
||||
readonly SelectionModel<AlgoDefinitionVM> _selected_algo_definition;
|
||||
@@ -60,8 +59,6 @@ namespace mbts.spark {
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly ListCollectionView<AlgoDefinitionVM> _algo_definitions_view, _favorite_algo_definitions_view;
|
||||
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(d => d.key);
|
||||
readonly EditableCollectionModel<AlgoDefinitionTemplateVM, AlgoDefinitionVM> _algo_template_collection_model;
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey> _favorite_templates = new ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey>(tvm => tvm.key);
|
||||
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
|
||||
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
|
||||
@@ -74,7 +71,6 @@ namespace mbts.spark {
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
security_source = _security.observe_current(true);
|
||||
_selected_algo_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_algo_template));
|
||||
_selected_algo_definition = new SelectionModel<AlgoDefinitionVM>(algo_definitions,
|
||||
() => {
|
||||
var t = _model.factory.default_order_type(security);
|
||||
@@ -131,29 +127,8 @@ namespace mbts.spark {
|
||||
order_properties.Select(ops => ops.time_in_force),
|
||||
() => model.factory.default_time_in_force(security),
|
||||
tif => time_in_forces.Contains(tif));
|
||||
_selected_favorite_algo_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_favorite_algo_template));
|
||||
// items source views
|
||||
algo_templates_view = CollectionViewSource.GetDefaultView(algo_templates);
|
||||
var favorite_algo_templates_view = new ListCollectionView(ObservableAggregateCollection.create(_algo_definitions.AsCollection(), d => d.templates));
|
||||
favorite_algo_templates_view.LiveGroupingProperties.Add(nameof(AlgoDefinitionTemplateVM.owner_key));
|
||||
favorite_algo_templates_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionTemplateVM.is_favorite));
|
||||
favorite_algo_templates_view.Filter = item => (item as AlgoDefinitionTemplateVM)?.is_favorite ?? false;
|
||||
favorite_algo_templates_view.IsLiveFiltering = true;
|
||||
this.favorite_algo_templates_view = favorite_algo_templates_view;
|
||||
// commands
|
||||
save_definition_cmd = new AsyncCommand<AlgoDefinitionVM>(save_definition_async);
|
||||
save_template_cmd = new AsyncCommand<AlgoDefinitionTemplateVM>(save_template_async);
|
||||
remove_template_cmd = new AsyncCommand<AlgoDefinitionTemplateVM>(tvm => _model.algo_model.remove_template(tvm.key));
|
||||
toggle_is_favorite_cmd = new DelegateCommand<AlgoDefinitionTemplateVM>(tvm => _favorite_templates.Add(tvm));
|
||||
_algo_template_collection_model = new EditableCollectionModel<AlgoDefinitionTemplateVM, AlgoDefinitionVM>(d => selected_algo_definition == null ? default : selected_algo_definition.template_factory(), d => d != null);
|
||||
redo_cmd = ObservantCommand.create(
|
||||
t => t?.redo(),
|
||||
t => t == null ? Observable.Return(false) : System.Reactive.Linq.ObservableExtensions.FromEvent(cb => t.can_redo_changed += cb, cb => t.can_redo_changed -= cb, () => t.can_redo).OnDispatcher(),
|
||||
out IObserver<AlgoDefinitionTemplateVM> redo_observer);
|
||||
undo_cmd = ObservantCommand.create(
|
||||
t => t?.undo(),
|
||||
t => t == null ? Observable.Return(false) : System.Reactive.Linq.ObservableExtensions.FromEvent(cb => t.can_undo_changed += cb, cb => t.can_undo_changed -= cb, () => t.can_undo).OnDispatcher(),
|
||||
out IObserver<AlgoDefinitionTemplateVM> undo_observer);
|
||||
undo_definition_cmd = ObservantCommand.create(
|
||||
d => d?.undo(),
|
||||
d => d == null ? Observable.Return(false) : System.Reactive.Linq.ObservableExtensions.FromEvent(cb => d.can_undo_changed += cb, cb => d.can_undo_changed -= cb, () => d.can_undo).OnDispatcher(),
|
||||
@@ -177,7 +152,7 @@ namespace mbts.spark {
|
||||
}, out IObserver<bool> revert_algo_params_observer);
|
||||
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
|
||||
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
|
||||
var inline_algo_relay = new SelectionModelInlinePropRelay<AlgoDefinitionVM, AlgoDefinitionVM, AlgoID>(_selected_algo_definition, vm => AlgoID.from_algo_key(vm.key, _model.dynamic_helper.dynamic_type_index));
|
||||
var inline_algo_relay = new SelectionModelInlinePropRelay<AlgoDefinitionVM, AlgoDefinitionVM, AlgoID>(_selected_algo_definition, vm => AlgoID.from_algo_key(vm.key));
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
order_properties.OnDispatcher().Subscribe(props => {
|
||||
@@ -186,31 +161,10 @@ namespace mbts.spark {
|
||||
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
|
||||
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
|
||||
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
|
||||
as_demuxer().subscribe<AlgoDefinitionTemplateVM>(nameof(selected_algo_template)).Subscribe(
|
||||
undo_observer,
|
||||
redo_observer),
|
||||
as_demuxer().subscribe<AlgoDefinitionVM>(nameof(selected_algo_definition)).Subscribe(
|
||||
undo_definition_observer,
|
||||
redo_definition_observer,
|
||||
revert_definition_observer),
|
||||
algo_definitions.Subscribe((s, e) => {
|
||||
if (e.OldItems != null)
|
||||
e.OldItems.OfType<IDisposable>().DisposeAll();
|
||||
if (e.NewItems != null) {
|
||||
foreach (AlgoDefinitionVM d in e.NewItems) {
|
||||
switch (d.key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
if (_model.algo_model.ui_configuration.include_custom_templates && _model.algo_model.templates_enabled())
|
||||
d.prepend_custom_template();
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (_model.algo_model.ui_configuration.include_legacy_custom_templates && _model.algo_model.templates_enabled())
|
||||
d.prepend_custom_template();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
security_md_source.Select(smd => model.factory.default_algos(smd.security)).Table(s => s.key).Subscribe(factory_defs => {
|
||||
using (_selected_algo_definition.begin_update())
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, factory_defs, fd => {
|
||||
@@ -222,43 +176,41 @@ namespace mbts.spark {
|
||||
model.algo_model.definitions.Subscribe(_definitions_buffer),
|
||||
_definitions_buffer.OnDispatcher().Subscribe(u => {
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, u, def_data => {
|
||||
var def_vm = AlgoDefinitionVM.create(model, def_data, true, OrderDataHelper.get_algo_format(security.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
var def_vm = AlgoDefinitionVM.create(model, def_data, OrderDataHelper.get_algo_format(security.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
def_vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
|
||||
def_vm.favorite_index = _get_favorite_algo_index(def_vm.key);
|
||||
return def_vm;
|
||||
});
|
||||
}),
|
||||
}),
|
||||
model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(_on_template_metadata),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_definitions, vm =>
|
||||
Disposable.Create(
|
||||
vm.connect_data(nullable_security_md_source),
|
||||
security_source.Subscribe(s => {
|
||||
var fmt = OrderDataHelper.get_algo_format(s.ContractCase);
|
||||
foreach (var d in algo_definitions)
|
||||
d.selected_format = fmt;
|
||||
}),
|
||||
vm), true)) {
|
||||
() => {
|
||||
Disposable.dispose(ref _template_collection_sync);
|
||||
Disposable.dispose(ref _algo_param_rows_hook);
|
||||
}
|
||||
Disposable.Create(
|
||||
vm.connect_data(nullable_security_md_source),
|
||||
security_source.Subscribe(s => {
|
||||
var fmt = OrderDataHelper.get_algo_format(s.ContractCase);
|
||||
foreach (var d in algo_definitions)
|
||||
d.selected_format = fmt;
|
||||
}),
|
||||
vm),
|
||||
true)) {
|
||||
() => Disposable.dispose(ref _algo_param_rows_hook)
|
||||
};
|
||||
// local methods
|
||||
async Task save_template_async(AlgoDefinitionTemplateVM template) {
|
||||
using (_preserve_selection_by_index())
|
||||
await AlgoPersistenceHelper.persist_template(model, algo_definitions, template);
|
||||
}
|
||||
async Task save_definition_async(AlgoDefinitionVM definition) {
|
||||
switch (definition.key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
await AlgoPersistenceHelper.persist_definition(model, algo_definitions, definition);
|
||||
using (_definitions_buffer.buffer_updates())
|
||||
await _model.algo_model.save_definition(definition.create_algo_def());
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
using (_definitions_buffer.buffer_updates()) {
|
||||
var persisted_key_lookup = await AlgoPersistenceHelper.persist_definition(model, algo_definitions, definition);
|
||||
if (persisted_key_lookup.TryGetValue(definition.key, out AlgoKey pkey) && _algo_definitions.TryGetValue(pkey, out AlgoDefinitionVM pdef))
|
||||
selected_algo_definition = pdef;
|
||||
var persisted_def = await model.algo_model.save_definition(definition.create_algo_def());
|
||||
if (persisted_def.HasValue) {
|
||||
int pos = algo_definitions.IndexOf(definition);
|
||||
if (pos >= 0)
|
||||
selected_algo_definition = algo_definitions[pos] = definition.to_persisted(persisted_def.Value);
|
||||
}
|
||||
if (definition.key.mode == AlgoKey.Mode.Factory) {
|
||||
/// Revert the factory definition to its original state.
|
||||
/// Note: this won't revert the parameters in the algo popup since the newly persisted definition (<see cref="pdef"/>) is selected now.
|
||||
@@ -283,32 +235,19 @@ namespace mbts.spark {
|
||||
new InlineUserProperty<AccountTuple>();
|
||||
}
|
||||
|
||||
IDisposable _template_collection_sync;
|
||||
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
|
||||
Disposable.dispose(ref _template_collection_sync);
|
||||
Disposable.dispose(ref _algo_param_rows_hook);
|
||||
if (new_value != null) {
|
||||
_template_collection_sync = ObservableAutoWrapper.connect_two_way(new_value.templates_with_custom, algo_templates);
|
||||
if (use_algo_param_grid)
|
||||
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
|
||||
if (reason != SelectionReason.Explicit)
|
||||
_selected_algo_stack.Push(new_value.key);
|
||||
} else {
|
||||
algo_templates.Clear();
|
||||
}
|
||||
if (open_algo_popup_on_select) {
|
||||
if (reason == SelectionReason.UserSelectedNewValue && new_value?.algo_root is IAlgoVM ar && ar.can_expand)
|
||||
ar.is_expanded = true;
|
||||
}
|
||||
if (selected_algo_template == null && _model.algo_model.templates_enabled()) {
|
||||
if (new_value != null)
|
||||
selected_algo_template = new_value.templates.FirstOrDefault(tvm => tvm.is_default, tvm => _favorite_templates.ContainsKey(tvm.key), tvm => true) ?? new_value.templates_with_custom.FirstOrDefault();
|
||||
} else {
|
||||
RaisePropertyChanged(nameof(selected_algo_template));
|
||||
}
|
||||
RaisePropertyChanged(nameof(selected_algo_definition), nameof(selected_favorite_algo_definition), nameof(can_expand), nameof(is_expanded));
|
||||
_selected_favorite_algo_template.Value = selected_algo_template is AlgoDefinitionTemplateVM t && t.is_favorite ? t : null;
|
||||
_algo_template_collection_model.OnNext(new_value);
|
||||
}
|
||||
|
||||
public SecurityImmutable security => _security.Value;
|
||||
@@ -316,7 +255,6 @@ namespace mbts.spark {
|
||||
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
|
||||
public ReadOnlyObservableCollection<TimeInForce> time_in_forces => _time_in_force.items;
|
||||
public TimeInForce selected_time_in_force { get => _time_in_force.selected_item; set => _time_in_force.selected_item = value; }
|
||||
public CustomOrderDefinitionImmutable? definition => selected_algo_template?.algo_root?.definition;
|
||||
public int favorite_algos_column_count { get => _favorite_algos_column_count; private set => Set(ref _favorite_algos_column_count, value); }
|
||||
public bool use_algo_param_grid {
|
||||
get => _use_algo_param_grid;
|
||||
@@ -331,47 +269,14 @@ namespace mbts.spark {
|
||||
}
|
||||
// items sources
|
||||
public ObservableCollection<AlgoDefinitionVM> algo_definitions => _algo_definitions;
|
||||
public ObservableCollection<AlgoDefinitionTemplateVM> algo_templates { get; } = new ObservableCollection<AlgoDefinitionTemplateVM>();
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> algo_param_rows => _algo_param_rows;
|
||||
public ICollectionView<AlgoDefinitionVM> algo_definitions_view => _algo_definitions_view;
|
||||
public ICollectionView<AlgoDefinitionVM> favorite_algo_definitions_view => _favorite_algo_definitions_view;
|
||||
public ICollectionView algo_templates_view { get; }
|
||||
public ICollectionView favorite_algo_templates_view { get; }
|
||||
// selected items
|
||||
public AlgoDefinitionVM selected_algo_definition {
|
||||
get => _selected_algo_definition.selected_item;
|
||||
set => _selected_algo_definition.selected_item = value;
|
||||
}
|
||||
public AlgoDefinitionTemplateVM selected_algo_template {
|
||||
get => selected_algo_definition?.selected_template;
|
||||
set {
|
||||
if (value != selected_algo_template) {
|
||||
if (value == null) {
|
||||
if (selected_algo_definition != null)
|
||||
selected_algo_definition.selected_template = null;
|
||||
_selected_favorite_algo_template.set(null);
|
||||
} else {
|
||||
value.owner.selected_template = value;
|
||||
_selected_algo_definition.select_explicit(value.owner); // idempotent
|
||||
_selected_favorite_algo_template.Value = value.is_favorite ? value : null; // idempotent
|
||||
}
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public AlgoDefinitionTemplateVM selected_favorite_algo_template {
|
||||
get => _selected_favorite_algo_template.Value;
|
||||
set {
|
||||
if (_selected_favorite_algo_template.set_new(value)) {
|
||||
if (value != null) {
|
||||
value.owner.selected_template = value;
|
||||
if (value.owner != selected_algo_definition)
|
||||
_selected_algo_definition.select_explicit(value.owner);
|
||||
RaisePropertyChanged(nameof(selected_algo_template));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public AlgoDefinitionVM selected_favorite_algo_definition {
|
||||
get => selected_algo_definition?.is_favorite ?? false ? selected_algo_definition : null;
|
||||
set {
|
||||
@@ -403,13 +308,10 @@ namespace mbts.spark {
|
||||
public ICommand revert_definition_cmd { get; }
|
||||
public ICommand set_default_algo_params_cmd { get; }
|
||||
public ICommand revert_algo_params_cmd { get; }
|
||||
public ICommand undo_cmd { get; }
|
||||
public ICommand redo_cmd { get; }
|
||||
public ICommand undo_definition_cmd { get; }
|
||||
public ICommand redo_definition_cmd { get; }
|
||||
public ICommand remove_template_cmd { get; }
|
||||
public ICommand toggle_is_favorite_cmd { get; }
|
||||
public IEditableCollectionModel<AlgoDefinitionTemplateVM> algo_template_collection_model => _algo_template_collection_model;
|
||||
|
||||
public IObservable<AccountTuple?> observe_account(bool start_immediately = false) =>
|
||||
_accounts.observe_current(start_immediately);
|
||||
@@ -418,9 +320,7 @@ namespace mbts.spark {
|
||||
_time_in_force.observe_current(start_immediately);
|
||||
|
||||
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
|
||||
public OrderDefinition create(Side side) {
|
||||
if (HasErrors)
|
||||
@@ -428,40 +328,29 @@ namespace mbts.spark {
|
||||
var order = _model.factory.order(security, selected_account);
|
||||
order.TimeInForce = selected_time_in_force;
|
||||
order.Side = side;
|
||||
if (_model.algo_model.templates_enabled()) {
|
||||
selected_algo_template.algo_root.assign_to(order.CustomDef);
|
||||
} else {
|
||||
var algo = new CustomOrderDefinition();
|
||||
selected_algo_definition.algo_root.assign_to(algo);
|
||||
switch (selected_algo_definition.key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
order.CustomDef = new CustomOrderDefinition {
|
||||
Composite = new CompositeOrderDefinition {
|
||||
AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint,
|
||||
Definition = algo
|
||||
}
|
||||
};
|
||||
selected_algo_definition.algo_root.assign_to(order.CustomDef.Composite.Definition);
|
||||
break;
|
||||
default:
|
||||
order.CustomDef = algo;
|
||||
break;
|
||||
}
|
||||
var algo = new CustomOrderDefinition();
|
||||
selected_algo_definition.algo_root.assign_to(algo);
|
||||
switch (selected_algo_definition.key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
order.CustomDef = new CustomOrderDefinition {
|
||||
Composite = new CompositeOrderDefinition {
|
||||
AlgoTemplateKey = selected_algo_definition.key.persisted,
|
||||
Definition = algo
|
||||
}
|
||||
};
|
||||
selected_algo_definition.algo_root.assign_to(order.CustomDef.Composite.Definition);
|
||||
break;
|
||||
default:
|
||||
order.CustomDef = algo;
|
||||
break;
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) =>
|
||||
_model.factory.get_metadata(order_type);
|
||||
|
||||
public void on_favorite_algos(LadderFavoriteAlgosSettings value) {
|
||||
_favorite_algos_index = value.favorite_algos.Select((v, i) => (v, i)).ToDictionaryCrushed(t => t.v, t => t.i);
|
||||
foreach (var d in algo_definitions) {
|
||||
d.favorite_index = _get_favorite_algo_index(d.key);
|
||||
foreach (var t in d.templates) {
|
||||
if (t.key.mode == AlgoKey.Mode.Persisted)
|
||||
t.is_favorite = _favorite_algos_index.ContainsKey(t.key);
|
||||
}
|
||||
}
|
||||
favorite_algos_column_count = value.column_count;
|
||||
}
|
||||
@@ -479,29 +368,7 @@ namespace mbts.spark {
|
||||
|
||||
void _on_template_metadata(IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> meta) {
|
||||
_algo_metadata = meta;
|
||||
var curr_selected_template = selected_algo_template;
|
||||
if (curr_selected_template?.is_favorite ?? false)
|
||||
selected_favorite_algo_template = curr_selected_template;
|
||||
algo_definitions_view.Refresh();
|
||||
}
|
||||
|
||||
// This preserves the selected items through a save op.
|
||||
// When a definition/template is persisted for the first time, its unpersisted-placeholder item is removed and replaced with a new persisted one.
|
||||
// If an unpersisted item is selected when it is removed, the view will automatically select null - this method is a workaround for that.
|
||||
IDisposable _preserve_selection_by_index() {
|
||||
(int? selected_def_index, int? selected_tmp_index) = AlgoPersistenceHelper.selection_snapshot(algo_definitions, selected_algo_definition, selected_algo_template);
|
||||
return new CompositeDisposable(_definitions_buffer.buffer_updates()) {
|
||||
() => {
|
||||
if (selected_def_index is int di && algo_definitions.check_index(di)) {
|
||||
var def = algo_definitions[di];
|
||||
if (selected_tmp_index is int ti && def.templates.check_index(ti)) {
|
||||
selected_algo_template = def.templates[ti];
|
||||
} else {
|
||||
selected_algo_definition = def;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,55 +4,28 @@ using System;
|
||||
|
||||
namespace bts.api.orderman {
|
||||
readonly struct OrderSecurity : IEquatable<OrderSecurity> {
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
piq = null;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
|
||||
security = t.sec;
|
||||
order = t.order;
|
||||
piq = null;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
this.piq = piq;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
this.piq = piq;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
root_meta = meta;
|
||||
}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? rootalgo = null, AlgoDefinitionMetadata? rootmeta = null) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
this.piq = piq;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
if (rootalgo.HasValue) {
|
||||
this.root_algo = rootalgo.Value;
|
||||
this.root_meta = rootmeta;
|
||||
} else {
|
||||
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
this.root_meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
|
||||
@@ -61,20 +34,13 @@ namespace bts.api.orderman {
|
||||
public uint? piq { get; }
|
||||
public AlgoData? algo { get; }
|
||||
public AlgoDefinitionMetadata? meta { get; }
|
||||
public AlgoData root_algo { get; }
|
||||
public AlgoDefinitionMetadata? root_meta { get; }
|
||||
|
||||
|
||||
public OrderSecurity with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
|
||||
new OrderSecurity(security, order, piq, algo, meta, root_algo, root_meta);
|
||||
|
||||
public bool Equals(OrderSecurity other) =>
|
||||
order == other.order &&
|
||||
security == other.security &&
|
||||
piq == other.piq &&
|
||||
algo == other.algo &&
|
||||
meta == other.meta &&
|
||||
root_algo == other.root_algo;
|
||||
meta == other.meta;
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
@@ -84,62 +50,104 @@ namespace bts.api.orderman {
|
||||
security?.GetHashCode() ?? 0,
|
||||
piq?.GetHashCode() ?? 0,
|
||||
algo?.GetHashCode() ?? 0,
|
||||
meta?.GetHashCode() ?? 0,
|
||||
root_algo.GetHashCode());
|
||||
meta?.GetHashCode() ?? 0);
|
||||
|
||||
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
|
||||
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
|
||||
|
||||
}
|
||||
|
||||
struct OrderSecurityAlgoMeta : IEquatable<OrderSecurityAlgoMeta> {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
readonly struct OrderSecurityAlgo : IEquatable<OrderSecurityAlgo> {
|
||||
public readonly OrderRecordImmutable order;
|
||||
public readonly SecurityImmutable? security;
|
||||
/// TODO: refactor <see cref="algo"/> to 'studio_algo' or something that indicates it does not bear the same relation to this order, that <see cref="root_algo"/> does to the root order.
|
||||
/// The former is meant to hold studio algo information, and nothing else. Naturally, it indicates whether or not this is a studio order by being non-null (<see cref="is_studio_algo"/>).
|
||||
/// The latter, however, can be any kind of algo (studio, exchange, etc.); it holds information about the root-most algo in this order's branch.
|
||||
public readonly AlgoData? algo;
|
||||
public readonly AlgoData algo;
|
||||
public readonly AlgoDefinitionMetadata? meta;
|
||||
public readonly AlgoData root_algo;
|
||||
public readonly AlgoDefinitionMetadata? root_meta;
|
||||
public readonly uint? piq;
|
||||
|
||||
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? root_algo = null, AlgoDefinitionMetadata? root_meta = null) {
|
||||
OrderSecurityAlgo(OrderRecordImmutable order, SecurityImmutable? security, AlgoData algo, AlgoDefinitionMetadata? meta, uint? piq) {
|
||||
this.order = order;
|
||||
this.security = security;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
if (root_algo.HasValue) {
|
||||
this.root_algo = root_algo.Value;
|
||||
this.root_meta = root_meta;
|
||||
} else {
|
||||
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
this.root_meta = meta;
|
||||
}
|
||||
this.piq = piq;
|
||||
}
|
||||
|
||||
public bool is_studio_algo => algo.HasValue;
|
||||
public static OrderSecurityAlgo from_order_security(OrderSecurity os) =>
|
||||
new OrderSecurityAlgo(
|
||||
os.order,
|
||||
os.security,
|
||||
os.algo ?? AlgoData.from_custom_def(os.order.Def.CustomDef),
|
||||
os.meta,
|
||||
os.piq);
|
||||
|
||||
public OrderSecurityAlgoMeta with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
|
||||
new OrderSecurityAlgoMeta(order, security, algo, meta, root_algo, root_meta);
|
||||
|
||||
public bool Equals(OrderSecurityAlgoMeta other) =>
|
||||
public bool Equals(OrderSecurityAlgo other) =>
|
||||
order == other.order &&
|
||||
security == other.security &&
|
||||
algo == other.algo &&
|
||||
meta == other.meta &&
|
||||
root_algo == other.root_algo;
|
||||
algo == other.algo;
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override bool Equals(object obj) =>
|
||||
obj is OrderSecurityAlgo other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
order.GetHashCode(),
|
||||
security?.GetHashCode() ?? 0,
|
||||
algo?.GetHashCode() ?? 0,
|
||||
meta?.GetHashCode() ?? 0,
|
||||
root_algo.GetHashCode());
|
||||
EqualityComparer.auto_phase(
|
||||
order.Key.GetHashCode(),
|
||||
security.HasValue ? security.Value.Id.GetHashCode() : 0,
|
||||
algo.GetHashCode());
|
||||
|
||||
public static bool operator ==(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => a.Equals(b);
|
||||
public static bool operator !=(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => !a.Equals(b);
|
||||
public static bool operator ==(OrderSecurityAlgo a, OrderSecurityAlgo b) => a.Equals(b);
|
||||
public static bool operator !=(OrderSecurityAlgo a, OrderSecurityAlgo b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
readonly struct OrderSecurityRootPair : IEquatable<OrderSecurityRootPair> {
|
||||
public readonly OrderSecurityAlgo order;
|
||||
readonly OrderSecurityAlgo? _root;
|
||||
|
||||
OrderSecurityRootPair(OrderSecurityAlgo order) {
|
||||
this.order = order;
|
||||
_root = null;
|
||||
}
|
||||
|
||||
OrderSecurityRootPair(OrderSecurityAlgo order, OrderSecurityAlgo? root) {
|
||||
this.order = order;
|
||||
_root = root;
|
||||
}
|
||||
|
||||
public OrderSecurityAlgo root => _root ?? order;
|
||||
|
||||
public static OrderSecurityRootPair from_order_security(OrderSecurity order) =>
|
||||
new OrderSecurityRootPair(OrderSecurityAlgo.from_order_security(order));
|
||||
|
||||
public static OrderSecurityRootPair from_order_security(OrderSecurity order, OrderSecurity root) =>
|
||||
new OrderSecurityRootPair(
|
||||
OrderSecurityAlgo.from_order_security(order),
|
||||
OrderSecurityAlgo.from_order_security(root));
|
||||
|
||||
public bool Equals(OrderSecurityRootPair other) =>
|
||||
order == other.order &&
|
||||
_root == other._root;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is OrderSecurityRootPair other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_root.HasValue ?
|
||||
(order.GetHashCode() % (1 << 15)) | (_root.Value.GetHashCode() % (1 << 15) << 16) :
|
||||
order.GetHashCode() % (1 << 15);
|
||||
|
||||
public static bool operator ==(OrderSecurityRootPair a, OrderSecurityRootPair b) => a.Equals(b);
|
||||
public static bool operator !=(OrderSecurityRootPair a, OrderSecurityRootPair b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class OrderSecurityHelper {
|
||||
public static readonly Func<OrderSecurity, uint>
|
||||
key_fn = o => o.order.Key,
|
||||
parent_key_fn = o => o.order.ParentKey;
|
||||
|
||||
public static readonly Func<OrderSecurityRootPair, uint>
|
||||
root_pair_key_fn = o => o.order.order.Key,
|
||||
root_pair_parent_key_fn = o => o.order.order.ParentKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using bts;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
|
||||
using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
|
||||
using CompositeDef = bts.api.orderman.CompositeOrderDefinition;
|
||||
using CompositeDefi = bts.api.orderman.CompositeOrderDefinitionImmutable;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class CompositeAlgoVM : OrderTypeVMBase<CompositeDef, CompositeDefi> {
|
||||
IDisposable _studio_def_hooks;
|
||||
bool _is_disposed;
|
||||
AlgoDefinitionMetadata? _studio_meta;
|
||||
|
||||
public CompositeAlgoVM(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(id, display_name, editing_permissions) { }
|
||||
|
||||
public override string abbreviation => _studio_meta?.abbreviation ?? base.abbreviation;
|
||||
public override OrderMetadata meta {
|
||||
get {
|
||||
var m = new OrderMetadata { KeyBinding = string.Empty, NoProps = false };
|
||||
if (_studio_meta.HasValue && _studio_meta.Value.badge_color.HasValue)
|
||||
m.Color = _studio_meta.Value.badge_color.Value.rgb_color();
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
public override void assign_to(AlgoDef value) {
|
||||
if (value.Composite == null)
|
||||
value.Composite = new CompositeDef();
|
||||
value.Composite.AlgoTemplateKey = id.persisted;
|
||||
base.assign_to(value);
|
||||
}
|
||||
protected override void Dispose(bool disposing) {
|
||||
_is_disposed = true;
|
||||
Disposable.dispose(ref _studio_def_hooks);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
protected override void on_model_internal() {
|
||||
Log.assert(!_is_disposed);
|
||||
_studio_def_hooks = Disposable.Create(model.algo_model.definitions.OnDispatcher().Subscribe(studio_def_map => {
|
||||
if (studio_def_map.try_get(id, out var data)) {
|
||||
display_name = data.name;
|
||||
load_from(data.value);
|
||||
}
|
||||
}),
|
||||
model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(studio_meta_map => {
|
||||
if (studio_meta_map.TryGetValue(id, out var m)) {
|
||||
var old_abb = abbreviation;
|
||||
var old_meta = meta;
|
||||
_studio_meta = m;
|
||||
if (abbreviation != old_abb)
|
||||
RaisePropertyChanged(nameof(abbreviation));
|
||||
if (meta != old_meta)
|
||||
RaisePropertyChanged(nameof(meta));
|
||||
}
|
||||
}));
|
||||
}
|
||||
protected override void on_security_internal() => base.on_security_internal();
|
||||
protected override void on_security_md_internal() => base.on_security_md_internal();
|
||||
protected override void validate() => base.validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace mbts.spark {
|
||||
|
||||
public event Action definition_changed;
|
||||
public IAlgoVM impl { get; private set; }
|
||||
public AlgoKey id { get; } = new AlgoKey(AlgoType.None);
|
||||
public AlgoKey id { get; } = AlgoKey.from_known_type(AlgoType.None);
|
||||
public AlgoType order_type => _definition.value.CustomCase;
|
||||
public OrderMetadata meta => OrderDataHelper.get_metadata(order_type);
|
||||
public bool can_expand => impl?.can_expand ?? false;
|
||||
@@ -58,8 +58,6 @@ namespace mbts.spark {
|
||||
public bool key_match(KeyEventArgs e) => false;
|
||||
public void load_from(AlgoDefi value) => impl?.load_from(value);
|
||||
public void load_many(IEnumerable<AlgoDefi> values) => impl?.load_many(values);
|
||||
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
|
||||
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
|
||||
void IAlgoVM.ensure_init() => impl?.ensure_init();
|
||||
|
||||
protected override void Dispose(bool disposing) =>
|
||||
@@ -96,7 +94,7 @@ namespace mbts.spark {
|
||||
if (impl != null && impl.order_type == fmt_type)
|
||||
break;
|
||||
_clear_impl();
|
||||
impl = OrderTypeVMFactory.create(new AlgoKey(fmt_type), fmt_type.display_name(), fmt_type, is_expanded: true);
|
||||
impl = OrderTypeVMFactory.create(AlgoKey.from_known_type(fmt_type), fmt_type.display_name(), fmt_type, is_expanded: true);
|
||||
_algo_params.add_collection(impl.algo_params);
|
||||
(impl as IObserver<OrdersModel>)?.OnNext(_model);
|
||||
break;
|
||||
|
||||
@@ -2,7 +2,6 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,26 +12,19 @@ using System.Reactive.Linq;
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class DynamicOrderHelper {
|
||||
public readonly IMaskIndexInt32<string> dynamic_type_index;
|
||||
|
||||
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
|
||||
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
|
||||
|
||||
readonly RegisteredDynamicOrderTypes _dynamic_types;
|
||||
|
||||
[ImportingConstructor]
|
||||
private DynamicOrderHelper(OrderService order_service)
|
||||
{
|
||||
DynamicOrderHelper(OrderService order_service) =>
|
||||
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
|
||||
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
|
||||
}
|
||||
|
||||
public IEnumerable<string> dynamic_types => _dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType);
|
||||
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory)
|
||||
{
|
||||
foreach (var d in _dynamic_types.DynamicOrderDesc)
|
||||
{
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs, Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory) {
|
||||
foreach (var d in _dynamic_types.DynamicOrderDesc) {
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
|
||||
var ddef = dorder.Dynamic;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -47,13 +47,10 @@ namespace mbts.spark {
|
||||
protected override void on_model_internal() =>
|
||||
_sync_dynamic_params();
|
||||
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_datum()
|
||||
{
|
||||
var data = OrderDataHelper.get_child_algo_data(new CustomOrderDefinition {
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_data() =>
|
||||
OrderDataHelper.get_child_algo_data(new CustomOrderDefinition {
|
||||
Dynamic = model?.dynamic_helper?.create_def(dyn_type) ?? new DynamicOrderDefinition()
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
void _sync_dynamic_params() {
|
||||
if (model != null) {
|
||||
@@ -106,19 +103,11 @@ namespace mbts.spark {
|
||||
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
|
||||
(mdef, v) => assign_to_param(mdef.OrderParams.GetOrAdd(pvm.descriptor.ParamName, pn => new DynamicOrderParam()), v),
|
||||
() => pvm.value,
|
||||
v => pvm.value = set_value_not_null_string(v),
|
||||
v => pvm.value = v,
|
||||
pvm.subscribe_value(),
|
||||
default_value);
|
||||
return pvm;
|
||||
}
|
||||
|
||||
T set_value_not_null_string<T>(T val)
|
||||
{
|
||||
if (typeof(T) == typeof(string) && val == null)
|
||||
return (T)(object)string.Empty;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using bts.api;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
internal class GenericOrderFactory
|
||||
@@ -81,7 +77,6 @@ namespace mbts.spark
|
||||
}, true));
|
||||
void on_param_prop_changed(object sender, PropertyChangedEventArgs e) => raise_definition_changed();
|
||||
_sync_generic_params();
|
||||
sync_selectors();
|
||||
}
|
||||
|
||||
public string algo_type => _algo_type.ToString();
|
||||
@@ -116,7 +111,7 @@ namespace mbts.spark
|
||||
desc.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName;
|
||||
}
|
||||
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_datum()
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_data()
|
||||
{
|
||||
var v = OrderDataHelper.get_child_algo_data(_algo_type);
|
||||
return v;
|
||||
@@ -130,7 +125,7 @@ namespace mbts.spark
|
||||
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName)
|
||||
{
|
||||
var qm = new GenericOrderQuantityVM<TMutable>(descriptor);
|
||||
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
|
||||
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value);
|
||||
}
|
||||
else if(descriptor.FieldType == FieldType.Enum)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,6 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -57,7 +56,6 @@ namespace mbts.spark {
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
dynamic children_vm { get; }
|
||||
bool key_match(KeyEventArgs e);
|
||||
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
|
||||
void load_many(IEnumerable<CustomOrderDefinitionImmutable> definitions);
|
||||
void ensure_init();
|
||||
}
|
||||
@@ -92,13 +90,9 @@ namespace mbts.spark {
|
||||
IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
|
||||
IAlgoVM selected_algo { get; set; }
|
||||
void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo);
|
||||
mbts.ui.spark.AlgoContentTemplateSelector.OrderKind order_kind {get;}
|
||||
}
|
||||
|
||||
static class OrderTypeVMExtensions {
|
||||
public static AlgoData create_def_data(this IAlgoVM vm) =>
|
||||
new AlgoData(vm.definition, vm.display_name, vm.id);
|
||||
|
||||
public static void load_structure(this IAlgoVM vm, CustomOrderDefinitionImmutable def) {
|
||||
var mdef = def.CloneToMutable();
|
||||
// assign values from vm.definition to mdef (values will be assigned at each common structure-level, e.g. Iceberg->Iceberg->Limit and Iceberg->Iceberg->Lean have 2 common structure-levels)
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class LimitOrderVM : OrderTypeVMBase<LimitOrderDefinition, LimitOrderDefinitionImmutable> {
|
||||
internal LimitOrderVM()
|
||||
: this(new AlgoKey(CustomOrderDefinition.CustomOneofCase.Limit), CustomOrderDefinition.CustomOneofCase.Limit.display_name()) { }
|
||||
: this(AlgoKey.from_known_type(CustomOrderDefinition.CustomOneofCase.Limit), CustomOrderDefinition.CustomOneofCase.Limit.display_name()) { }
|
||||
|
||||
internal LimitOrderVM(AlgoKey key, string display_name)
|
||||
: base(key, display_name, AlgoEditingPermissions.None) { }
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class MarketOrderVM : OrderTypeVMBase<MarketOrderDefinition, MarketOrderDefinitionImmutable> {
|
||||
internal MarketOrderVM()
|
||||
: this (new AlgoKey(CustomOrderDefinition.CustomOneofCase.Market), CustomOrderDefinition.CustomOneofCase.Market.display_name()) { }
|
||||
: this (AlgoKey.from_known_type(CustomOrderDefinition.CustomOneofCase.Market), CustomOrderDefinition.CustomOneofCase.Market.display_name()) { }
|
||||
|
||||
internal MarketOrderVM(AlgoKey key, string display_name)
|
||||
: base(key, display_name, AlgoEditingPermissions.None) { }
|
||||
|
||||
@@ -21,7 +21,6 @@ using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
|
||||
using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
|
||||
@@ -57,19 +56,11 @@ namespace mbts.spark {
|
||||
public static IAlgoVM iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new IcebergOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM spreader(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new SpreaderOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
|
||||
public static IAlgoVM splitter(AlgoKey key, string display_name, AlgoEditingPermissions permissions) =>
|
||||
new SplitterAlgoVM(key, display_name, permissions);
|
||||
|
||||
public static IAlgoVM generic(AlgoKey key, string display_name,
|
||||
AlgoType algo_type, AlgoEditingPermissions editing_permissions)
|
||||
{
|
||||
return GenericOrderFactory.create_vm(key, algo_type, display_name, editing_permissions);
|
||||
}
|
||||
public static IAlgoVM splitter(AlgoKey key, string display_name, AlgoEditingPermissions permissions) => new SplitterAlgoVM(key, display_name, permissions);
|
||||
public static IAlgoVM generic(AlgoKey key, string display_name, AlgoType algo_type, AlgoEditingPermissions editing_permissions) => GenericOrderFactory.create_vm(key, algo_type, display_name, editing_permissions);
|
||||
public static IAlgoVM composite(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new CompositeAlgoVM(key, display_name, editing_permissions);
|
||||
|
||||
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
|
||||
if (def_data.value.CustomCase == AlgoType.Composite) {
|
||||
def_data = new AlgoData(def_data.value.Composite.Definition, def_data.name, def_data.key);
|
||||
}
|
||||
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
|
||||
vm.load_from(def_data.value);
|
||||
return vm;
|
||||
@@ -97,8 +88,11 @@ namespace mbts.spark {
|
||||
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
case AlgoType.Composite: vm = composite(key, name, editing_permissions); 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}.'");
|
||||
// return new DefaultAlgoVM();
|
||||
}
|
||||
vm.is_expanded = is_expanded && vm.can_expand;
|
||||
return vm;
|
||||
@@ -133,6 +127,7 @@ namespace mbts.spark {
|
||||
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
|
||||
readonly Property<bool> _is_expanded;
|
||||
readonly DeferrableActionWrapper _def_change_notifier;
|
||||
string _display_name;
|
||||
bool _is_initialized;
|
||||
SecurityInput _security_input;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
@@ -140,11 +135,10 @@ namespace mbts.spark {
|
||||
|
||||
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
_display_name = display_name;
|
||||
_editing_permissions = editing_permissions;
|
||||
_child_algo_selectors = new ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData>(_child_algo_selector_data, sd => sd.selector);
|
||||
_is_expanded = backing<bool>(nameof(is_expanded));
|
||||
meta = OrderDataHelper.get_metadata(order_type);
|
||||
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
|
||||
PropertyChanged += (s, e) => {
|
||||
switch (e.PropertyName) {
|
||||
@@ -170,16 +164,16 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref def_sub);
|
||||
}
|
||||
};
|
||||
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
|
||||
RaisePropertyChanged(sd.prop_name);
|
||||
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)
|
||||
def_sub.Add(ndei.subscribe_has_errors().Subscribe(has_errors => assert(!has_errors, sd.prop_name, "Algo has invalid parameters.")));
|
||||
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
|
||||
RaisePropertyChanged(sd.prop_name);
|
||||
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)
|
||||
def_sub.Add(ndei.subscribe_has_errors().Subscribe(has_errors => assert(!has_errors, sd.prop_name, "Algo has invalid parameters.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, true));
|
||||
}, true));
|
||||
}
|
||||
|
||||
public event Action definition_changed;
|
||||
@@ -190,11 +184,11 @@ namespace mbts.spark {
|
||||
public AlgoKey id { get; }
|
||||
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
|
||||
// display
|
||||
public string display_name { get; private set; }
|
||||
public string display_name { get => _display_name; protected set => Set(ref _display_name, value); }
|
||||
public virtual string abbreviation => model == null ? display_name : model.factory.abbreviate_algo(new OneOf<AlgoType, string>(order_type));
|
||||
public virtual AlgoDisplayGroup group => order_type.is_bts_algo_type() ? AlgoDisplayGroup.BTS : AlgoDisplayGroup.Exchange;
|
||||
public OrdersModel orders_model => model;
|
||||
public OrderMetadata meta { get; }
|
||||
public virtual OrderMetadata meta => OrderDataHelper.get_metadata(order_type);
|
||||
// child algos
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public dynamic children_vm {
|
||||
@@ -221,7 +215,7 @@ namespace mbts.spark {
|
||||
protected SecurityMD? security_md => _security_input.security_md;
|
||||
protected SecurityImmutable? security => _security_input.security;
|
||||
|
||||
public void assign_to(AlgoDef value) {
|
||||
public virtual void assign_to(AlgoDef value) {
|
||||
if (!(_descriptor.Accessor.GetValue(value) is TMutable algo))
|
||||
_descriptor.Accessor.SetValue(value, algo = new TMutable());
|
||||
ensure_init();
|
||||
@@ -261,50 +255,31 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
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(algom))));
|
||||
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))));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase, is_expanded: is_expanded));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
|
||||
if (defi.CustomCase != order_type) {
|
||||
defi = defi.actual();
|
||||
if (defi.CustomCase != order_type)
|
||||
return null;
|
||||
}
|
||||
using (_def_change_notifier.defer_execution()) {
|
||||
// TODO: each level of the order hierarchy could load in parallel
|
||||
await on_load_from_async(defi);
|
||||
foreach (var sd in _child_algo_selector_data) {
|
||||
var inner_def = sd.get_order(defi);
|
||||
if (inner_def.HasValue) {
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase, is_expanded: is_expanded));
|
||||
await vm.load_from_async(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var ival = inner_def.Value;
|
||||
var vm = sd.selector.algos.GetOrAdd(
|
||||
AlgoKey.from_custom_def(ival),
|
||||
k => create(k, ival.display_name(), ival.CustomCase, is_expanded: is_expanded));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
var algom = _descriptor.Accessor.GetValue(defi.CloneToMutable()) as TMutable ?? new TMutable();
|
||||
foreach (var p in _auto_props)
|
||||
p.load(algom);
|
||||
return defi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,12 +298,11 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
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))));
|
||||
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))));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
// **assume that algos has more than 1 element
|
||||
@@ -343,11 +317,11 @@ namespace mbts.spark {
|
||||
init = OrderDataHelper.fit_to_type(init.Value, security.Value.ContractCase);
|
||||
if (!init.HasValue)
|
||||
return;
|
||||
key = OrderDataHelper.get_shallow_factory_key(init.Value);
|
||||
key = AlgoKey.from_custom_def(init.Value);
|
||||
child_list.Add(init.Value);
|
||||
for (int i = 1; i < algos.Count; i++) {
|
||||
var child = sd.get_from_impl(algos[i]);
|
||||
if (!child.HasValue || key != OrderDataHelper.get_shallow_factory_key(child.Value))
|
||||
if (!child.HasValue || key != AlgoKey.from_custom_def(child.Value))
|
||||
return;
|
||||
child = OrderDataHelper.fit_to_type(child.Value, security.Value.ContractCase);
|
||||
if (!child.HasValue)
|
||||
@@ -355,11 +329,11 @@ namespace mbts.spark {
|
||||
child_list.Add(child.Value);
|
||||
}
|
||||
} else {
|
||||
key = OrderDataHelper.get_shallow_factory_key(init.Value);
|
||||
key = AlgoKey.from_custom_def(init.Value);
|
||||
child_list.Add(init.Value);
|
||||
for (int i = 1; i < algos.Count; i++) {
|
||||
var child = sd.get_from_impl(algos[i]);
|
||||
if (!child.HasValue || key != OrderDataHelper.get_shallow_factory_key(child.Value))
|
||||
if (!child.HasValue || key != AlgoKey.from_custom_def(child.Value))
|
||||
return;
|
||||
child_list.Add(child.Value);
|
||||
}
|
||||
@@ -370,8 +344,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
|
||||
|
||||
protected void raise_definition_changed() =>
|
||||
_def_change_notifier.execute();
|
||||
|
||||
@@ -381,14 +353,14 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _resources);
|
||||
|
||||
#region Child Algo Selectors
|
||||
protected virtual IEnumerable<ChildAlgoData> get_selector_datum() =>
|
||||
protected virtual IEnumerable<ChildAlgoData> get_selector_data() =>
|
||||
OrderDataHelper.get_child_algo_data(order_type);
|
||||
|
||||
protected void sync_selectors() {
|
||||
if (!_is_initialized)
|
||||
return;
|
||||
using (reset()) {
|
||||
foreach (var c in get_selector_datum()) {
|
||||
foreach (var c in get_selector_data()) {
|
||||
_children_vm[c.name] = mk_selector(c.name, c.get_child_from_impl, new Func<AlgoDefi, AlgoDefi?>(defi => {
|
||||
var mchild = c.get_child(defi.CloneToMutable());
|
||||
if (mchild != null)
|
||||
@@ -841,7 +813,7 @@ namespace mbts.spark {
|
||||
void _populate_selectors() {
|
||||
if (model != null && security.HasValue) {
|
||||
var algo_data = model.factory.default_algos(security.Value);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
|
||||
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.replace(
|
||||
@@ -959,8 +931,6 @@ namespace mbts.spark {
|
||||
|
||||
public string id { get; }
|
||||
public string display_name { get; }
|
||||
public mbts.ui.spark .AlgoContentTemplateSelector.OrderKind order_kind =>
|
||||
new mbts.ui.spark.AlgoContentTemplateSelector.OrderKind(display_name, string.Empty);
|
||||
public bool is_enabled { get; set; } = true;
|
||||
|
||||
public bool initialized { get; private set; }
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value) => base.assign_to(value);
|
||||
|
||||
protected override void validate() {
|
||||
base.validate();
|
||||
if (!template.HasValue)
|
||||
|
||||
@@ -154,8 +154,6 @@ namespace mbts.spark {
|
||||
public static bool can_detach(this OrderRecordImmutable order) =>
|
||||
order.attached_target_order_key() == 0 && order.has_parent();
|
||||
|
||||
public static string display_name(this OrderSecurityAlgoMeta order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
public static string display_name(this OrderSecurity order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
|
||||
@@ -243,6 +241,9 @@ namespace mbts.spark {
|
||||
public static string type_id(this CustomOrderDefinition.CustomOneofCase order_type) =>
|
||||
order_type.ToString().ToUpper();
|
||||
|
||||
public static string display_name(this OrderSecurityAlgo order_sec_algo) =>
|
||||
display_name(order_sec_algo.order, order_sec_algo.algo);
|
||||
|
||||
public static string display_name(this CustomOrderDefinitionImmutable algo) =>
|
||||
algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? algo.Dynamic.DynamicOrderType : algo.CustomCase.display_name();
|
||||
|
||||
|
||||
@@ -92,39 +92,45 @@ namespace mbts.spark {
|
||||
(algo, k) => (algo, null),
|
||||
t => t.algo.key)
|
||||
.Cache();
|
||||
var order_secs_algo_meta_join = on_orders.LeftJoin(
|
||||
// join orders with algo data/metadata
|
||||
on_order_tuples = on_orders.LeftJoin(
|
||||
studio_algos_with_meta,
|
||||
t => t.order.Key,
|
||||
am => am.algo.key,
|
||||
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.algo_template_key())),
|
||||
t => AlgoKey.from_composite_key(t.order.algo_template_key()),
|
||||
(os, okey, am, mkey) => new OrderSecurity(os.sec, os.order, null, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurity(os.sec, os.order, null, null, null),
|
||||
ot => ot.order.Key);
|
||||
on_order_tuples = order_secs_algo_meta_join.Cache();
|
||||
ot => ot.order.Key)
|
||||
.Cache();
|
||||
|
||||
on_order_tuples_with_piq = on_order_tuples.LeftJoin<
|
||||
OrderSecurity,
|
||||
OrderPIQImmutable,
|
||||
OrderSecurity,
|
||||
uint
|
||||
>(mbo_service.subscribei_piq().Table(),
|
||||
osam => osam.order.Key,
|
||||
piq => piq.OrderId,
|
||||
(osam, _, p) => new OrderSecurity(osam.security, osam.order, p.Position, osam.algo, osam.meta),
|
||||
(osam, _) => new OrderSecurity(osam.security, osam.order, null, osam.algo, osam.meta),
|
||||
osp => osp.order.Key)
|
||||
.Cache();
|
||||
uint>(
|
||||
mbo_service.subscribei_piq().Table(),
|
||||
OrderSecurityHelper.key_fn,
|
||||
piq => piq.OrderId,
|
||||
(osam, _, p) => new OrderSecurity(osam.security, osam.order, p.Position, osam.algo, osam.meta),
|
||||
(osam, _) => new OrderSecurity(osam.security, osam.order, null, osam.algo, osam.meta),
|
||||
osp => osp.order.Key
|
||||
).Cache();
|
||||
|
||||
// order tuples with root
|
||||
on_order_tuples_with_root = on_order_tuples.RootDependent(
|
||||
o => o.order.Key,
|
||||
o => o.order.ParentKey,
|
||||
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
|
||||
OrderSecurityHelper.key_fn,
|
||||
OrderSecurityHelper.parent_key_fn,
|
||||
OrderSecurityRootPair.from_order_security,
|
||||
OrderSecurityRootPair.from_order_security,
|
||||
OrderSecurityHelper.root_pair_key_fn
|
||||
).Cache();
|
||||
// order tuples with root and piq
|
||||
on_order_tuples_with_root_and_piq = on_order_tuples_with_piq.RootDependent(
|
||||
o => o.order.Key,
|
||||
o => o.order.ParentKey,
|
||||
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
|
||||
OrderSecurityHelper.key_fn,
|
||||
OrderSecurityHelper.parent_key_fn,
|
||||
OrderSecurityRootPair.from_order_security,
|
||||
OrderSecurityRootPair.from_order_security,
|
||||
OrderSecurityHelper.root_pair_key_fn
|
||||
).Cache();
|
||||
// fills
|
||||
var on_fills =
|
||||
@@ -136,7 +142,7 @@ namespace mbts.spark {
|
||||
var fill_order_tuples_join = on_fills.LeftJoin(
|
||||
on_order_tuples,
|
||||
f => f.val.FillKey,
|
||||
o => o.order.Key,
|
||||
OrderSecurityHelper.key_fn,
|
||||
f => f.val.OrderKey,
|
||||
(f, fk, o, ok) => new FillTuple(f, o),
|
||||
(f, fk, ok) => new FillTuple(f, null),
|
||||
@@ -199,8 +205,8 @@ namespace mbts.spark {
|
||||
public IObservable<OrderUpdate> on_orders { get; }
|
||||
public IObservable<OrderUpdatePiq> on_order_tuples_with_piq { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples_with_root { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples_with_root_and_piq { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityRootPair, uint>> on_order_tuples_with_root { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityRootPair, uint>> on_order_tuples_with_root_and_piq { get; }
|
||||
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_detach { get; }
|
||||
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_attach { get; }
|
||||
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace mbts.collections {
|
||||
interface IObservableKeyedCollection<T, TKey> : IObservableCollection<T>, IDictionary<T, TKey> { }
|
||||
|
||||
class ObservableKeyedCollection<T, TKey> : ObservableCollection<T>, IObservableCollection<T>, IDictionary<TKey, T>, IReadOnlyObservableKeyedCollection<T, TKey> {
|
||||
const string _indexer_name = "Item[]";
|
||||
readonly Func<T, TKey> _key;
|
||||
readonly Dictionary<TKey, T> _map;
|
||||
|
||||
@@ -41,7 +40,7 @@ namespace mbts.collections {
|
||||
int index = Items.IndexOf(curr);
|
||||
Items[index] = value;
|
||||
_map[key] = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(_indexer_name));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(CollectionHelper.indexer_name));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, curr, index));
|
||||
} else {
|
||||
Items.Add(value);
|
||||
@@ -93,7 +92,7 @@ namespace mbts.collections {
|
||||
_map.Remove(old_key);
|
||||
_map[_key(new_value)] = new_value;
|
||||
Items[index] = new_value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(_indexer_name));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(CollectionHelper.indexer_name));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, new_value, old_value, index));
|
||||
return true;
|
||||
}
|
||||
@@ -188,7 +187,7 @@ namespace mbts.collections {
|
||||
void _set(T item) => _map[_key(item)] = item;
|
||||
void _notify_props_changed() {
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(_indexer_name));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(CollectionHelper.indexer_name));
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key) => _map.ContainsKey(key);
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.collections
|
||||
{
|
||||
public class ObservableSortedCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
public ObservableSortedCollection(ObservableCollection<T> source) : this(source, Comparer<T>.Default) { }
|
||||
|
||||
public ObservableSortedCollection(ObservableCollection<T> source, IComparer<T> comparer) {
|
||||
_comparer = comparer;
|
||||
foreach (var item in source) _insert_sorted(item);
|
||||
source.CollectionChanged += source_CollectionChanged;
|
||||
}
|
||||
|
||||
private IComparer<T> _comparer;
|
||||
public IComparer<T> comparer { get { return _comparer; } set { _comparer = value; sort(); } }
|
||||
|
||||
protected void sort() { this.sync_with(this.OrderBy(t => t, _comparer)); }
|
||||
|
||||
private void source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: foreach (T item in e.NewItems) _insert_sorted(item); return;
|
||||
case NotifyCollectionChangedAction.Remove: foreach (T item in e.OldItems) Remove(item); return;
|
||||
default:
|
||||
if (e.OldItems != null) foreach (T item in e.OldItems) Remove(item);
|
||||
if (e.NewItems != null) foreach (T item in e.NewItems) _insert_sorted(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void _insert_sorted(T item) { _insert_sorted(item, 0, Count); }
|
||||
// Takes advantage of the fact that all elements are assumed to be sorted, recursively hones in on the insert index.
|
||||
private void _insert_sorted(T item, int index_range_begin, int index_range_end) {
|
||||
if (index_range_begin == index_range_end) { Insert(index_range_begin, item); return; }
|
||||
if (index_range_end == (index_range_begin + 1)) {
|
||||
if (_comparer.Compare(item, this[index_range_begin]) <= 0) Insert(index_range_begin, item);
|
||||
else Insert(index_range_end, item);
|
||||
return;
|
||||
}
|
||||
int middle_index = index_range_begin + (int)((index_range_end - index_range_begin) * 0.5);
|
||||
int comp = _comparer.Compare(item, this[middle_index]);
|
||||
if (comp > 0) _insert_sorted(item, middle_index + 1, index_range_end);
|
||||
else if (comp < 0) {
|
||||
if (_comparer.Compare(item, this[middle_index - 1]) >= 0) { Insert(middle_index, item); return; }
|
||||
_insert_sorted(item, index_range_begin, middle_index - 1);
|
||||
}
|
||||
else { Insert(middle_index, item); return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,19 @@
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Threading.Tasks
|
||||
{
|
||||
public static class NonGenericTaskExtensions
|
||||
{
|
||||
namespace System.Threading.Tasks {
|
||||
public static class NonGenericTaskExtensions {
|
||||
public static Task ContinueOnDispatcher(this Task task, Action<Task> continuationAction) {
|
||||
return task.ContinueWith(continuationAction, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
public static Task<TResult> SelectOnDispatcher<TResult>(this Task task, Func<Task, TResult> selector) =>
|
||||
task.ContinueWith(selector, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
|
||||
public static Task<TResult> SelectOnDispatcherAsync<TResult>(this Task task, Func<Task, Task<TResult>> selector_async) =>
|
||||
task.ContinueWith(selector_async, TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
|
||||
}
|
||||
|
||||
public static class GenericTaskExtensions
|
||||
{
|
||||
public static class GenericTaskExtensions {
|
||||
public static Task ContinueOnDispatcher<T>(this Task<T> task, Action<Task<T>> continuationAction) {
|
||||
return task.ContinueWith(continuationAction, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
@@ -29,8 +27,7 @@ namespace System.Threading.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
public static class TaskHelper
|
||||
{
|
||||
public static class TaskHelper {
|
||||
public static async void DelayAction(TimeSpan delay, Action action, CancellationTokenSource cts) {
|
||||
await Task.Delay(delay);
|
||||
if (cts == null || !cts.IsCancellationRequested) action();
|
||||
|
||||
@@ -94,6 +94,13 @@ namespace bts.utils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (_values.Count > 0) {
|
||||
_values.Clear();
|
||||
_update.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CommonValueGroupMonitor<T, TKey> where TKey : struct {
|
||||
@@ -104,6 +111,7 @@ namespace bts.utils {
|
||||
RefCountMap<T> _deferred_adds, _deferred_removes;
|
||||
int _deferral_count;
|
||||
bool _is_deferred => _deferral_count > 0;
|
||||
bool _reset_requested;
|
||||
|
||||
public CommonValueGroupMonitor(Func<T, TKey> key)
|
||||
: this(key, EqualityComparer<TKey>.Default, EqualityComparer<T>.Default) { }
|
||||
@@ -172,12 +180,37 @@ namespace bts.utils {
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (_is_deferred) {
|
||||
_deferred_adds.Clear();
|
||||
_deferred_removes.Clear();
|
||||
_reset_requested = true;
|
||||
return;
|
||||
}
|
||||
switch (_value_groups.Count) {
|
||||
case 1:
|
||||
_value_groups.Clear();
|
||||
common_values_changed?.Invoke((Enumerable.Empty<T>(), Enumerable.Empty<T>()));
|
||||
common_value_group_changed?.Invoke(null);
|
||||
break;
|
||||
default:
|
||||
_value_groups.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable deferr_updates() {
|
||||
var last_fired_common_key = common_value_group?.Key;
|
||||
++_deferral_count;
|
||||
return Disposable.Create(() => {
|
||||
if (--_deferral_count == 0 && (_deferred_adds.Count > 0 || _deferred_removes.Count > 0)) {
|
||||
foreach(var kv in _deferred_removes) {
|
||||
if (--_deferral_count > 0 || (_deferred_adds.Count == 0 && _deferred_removes.Count == 0 && !_reset_requested))
|
||||
return;
|
||||
if (_reset_requested) {
|
||||
_value_groups.Clear();
|
||||
_reset_requested = false;
|
||||
}
|
||||
if (_deferred_adds.Count > 0 || _deferred_removes.Count > 0) {
|
||||
foreach (var kv in _deferred_removes) {
|
||||
var k = _key(kv.Key);
|
||||
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
|
||||
rcm.decrement(kv.Key, kv.Value);
|
||||
@@ -192,78 +225,140 @@ namespace bts.utils {
|
||||
}
|
||||
rcm.increment(kv.Key, kv.Value);
|
||||
}
|
||||
if (!EqualityComparer.nullable_equals(last_fired_common_key, common_value_group?.Key)) {
|
||||
common_value_group_changed?.Invoke(common_value_group);
|
||||
} else if (last_fired_common_key is TKey k) {
|
||||
common_values_changed?.Invoke((
|
||||
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
|
||||
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
|
||||
}
|
||||
_deferred_adds = new RefCountMap<T>(_value_comparer);
|
||||
_deferred_removes = new RefCountMap<T>(_value_comparer);
|
||||
}
|
||||
if (!EqualityComparer.nullable_equals(last_fired_common_key, common_value_group?.Key)) {
|
||||
common_value_group_changed?.Invoke(common_value_group);
|
||||
} else if (last_fired_common_key is TKey k) {
|
||||
common_values_changed?.Invoke((
|
||||
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
|
||||
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
|
||||
}
|
||||
_deferred_adds = new RefCountMap<T>(_value_comparer);
|
||||
_deferred_removes = new RefCountMap<T>(_value_comparer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CommonValueMonitorMultiplexor<TSource> {
|
||||
readonly Dictionary<int, IMonitor> _members = new Dictionary<int, IMonitor>();
|
||||
int _member_id_counter;
|
||||
readonly Dictionary<int, (Action<TSource> add, Action<TSource> remove, Func<IDisposable> defer)> _members = new Dictionary<int, (Action<TSource> add, Action<TSource> remove, Func<IDisposable> defer)>();
|
||||
|
||||
public IDisposable defer_updates() =>
|
||||
new CompositeDisposable(_members.Values.Select(v => v.defer()));
|
||||
|
||||
public void add(TSource value) {
|
||||
foreach ((var add, var remove, var defer) in _members.Values)
|
||||
add(value);
|
||||
foreach (var m in _members.Values)
|
||||
m.add(value);
|
||||
}
|
||||
|
||||
public void remove(TSource value) {
|
||||
foreach ((var add, var remove, var defer) in _members.Values)
|
||||
remove(value);
|
||||
foreach (var m in _members.Values)
|
||||
m.remove(value);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
foreach (var m in _members.Values)
|
||||
m.clear();
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value) {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => member_mon.add(get_value(v)),
|
||||
v => member_mon.remove(get_value(v)),
|
||||
member_mon.defer_updates));
|
||||
_members.Add(id, new SelectorMon<T>(member_mon, get_value));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value, Func<T, bool> where) {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
if (where(res))
|
||||
member_mon.add(res);
|
||||
},
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
if (where(res))
|
||||
member_mon.remove(res);
|
||||
},
|
||||
member_mon.defer_updates));
|
||||
_members.Add(id, new WhereMon<T>(member_mon, get_value, where));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T, TKey>(CommonValueGroupMonitor<T, TKey> member_mon, Func<TSource, T> get_value) where TKey : struct {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
member_mon.add(res);
|
||||
},
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
member_mon.remove(res);
|
||||
},
|
||||
member_mon.deferr_updates
|
||||
));
|
||||
_members.Add(id, new SelectorGroupMon<T, TKey>(member_mon, get_value));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
|
||||
interface IMonitor {
|
||||
void add(TSource value);
|
||||
void remove(TSource value);
|
||||
void clear();
|
||||
IDisposable defer();
|
||||
}
|
||||
|
||||
class SelectorMon<T> : IMonitor {
|
||||
readonly CommonValueMonitor<T> _monitor;
|
||||
readonly Func<TSource, T> _get_value;
|
||||
|
||||
public SelectorMon(CommonValueMonitor<T> monitor, Func<TSource, T> get_value) {
|
||||
_monitor = monitor;
|
||||
_get_value = get_value;
|
||||
}
|
||||
|
||||
public void add(TSource value) =>
|
||||
_monitor.add(_get_value(value));
|
||||
|
||||
public void remove(TSource value) =>
|
||||
_monitor.remove(_get_value(value));
|
||||
|
||||
public void clear() =>
|
||||
_monitor.clear();
|
||||
|
||||
public IDisposable defer() =>
|
||||
_monitor.defer_updates();
|
||||
}
|
||||
|
||||
class SelectorGroupMon<T, TKey> : IMonitor where TKey : struct {
|
||||
readonly CommonValueGroupMonitor<T, TKey> _monitor;
|
||||
readonly Func<TSource, T> _get_value;
|
||||
|
||||
public SelectorGroupMon(CommonValueGroupMonitor<T, TKey> monitor, Func<TSource, T> get_value) {
|
||||
_monitor = monitor;
|
||||
_get_value = get_value;
|
||||
}
|
||||
|
||||
public void add(TSource value) =>
|
||||
_monitor.add(_get_value(value));
|
||||
|
||||
public void remove(TSource value) =>
|
||||
_monitor.remove(_get_value(value));
|
||||
|
||||
public void clear() =>
|
||||
_monitor.clear();
|
||||
|
||||
public IDisposable defer() =>
|
||||
_monitor.deferr_updates();
|
||||
}
|
||||
|
||||
class WhereMon<T> : IMonitor {
|
||||
readonly CommonValueMonitor<T> _monitor;
|
||||
readonly Func<T, bool> _where;
|
||||
readonly Func<TSource, T> _selector;
|
||||
|
||||
public WhereMon(CommonValueMonitor<T> monitor, Func<TSource, T> selector, Func<T, bool> where) {
|
||||
_monitor = monitor;
|
||||
_where = where;
|
||||
_selector = selector;
|
||||
}
|
||||
|
||||
public void add(TSource value) {
|
||||
var r = _selector(value);
|
||||
if (_where(r))
|
||||
_monitor.add(r);
|
||||
}
|
||||
|
||||
public void remove(TSource value) {
|
||||
var r = _selector(value);
|
||||
if (_where(r))
|
||||
_monitor.remove(r);
|
||||
}
|
||||
|
||||
public void clear() =>
|
||||
_monitor.clear();
|
||||
|
||||
public IDisposable defer() =>
|
||||
_monitor.defer_updates();
|
||||
}
|
||||
}
|
||||
|
||||
static class CommonValueMonitorExtensions {
|
||||
@@ -284,8 +379,9 @@ namespace bts.utils {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
o.OnNext(monitor.common_value);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
@@ -304,8 +400,9 @@ namespace bts.utils {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
@@ -326,8 +423,9 @@ namespace bts.utils {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
@@ -354,8 +452,9 @@ namespace bts.utils {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
@@ -379,8 +478,9 @@ namespace bts.utils {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
o.OnNext(monitor.plurality);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
|
||||
@@ -17,15 +17,18 @@ using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public class Badge : Control {
|
||||
public const double _default_border_double = 2;
|
||||
public const double LINE_SPACING = 1;
|
||||
const string _hedge_indicator = "H";
|
||||
// Storing the defaults here for readability
|
||||
// Warning: these properties are used to calculate the ladder's row_height. Please change the values below if you want to change properties that affect the layout.
|
||||
// ** - value is not the actual PropertyMetadata.DefaultValue
|
||||
// - DefaultMargin is used by the LadderOrdersHost.get_panel when assigning StackPanel's Margin
|
||||
// - That StackPanel's Margin needs to account for the badge border
|
||||
public static readonly Thickness
|
||||
DefaultMargin = new Thickness(4, 2, 4, 2), // **
|
||||
DefaultMargin = new Thickness(_default_border_double, _default_border_double, _default_border_double, _default_border_double), // **
|
||||
DefaultPadding = new Thickness(1),
|
||||
DefaultBorderThickness = new Thickness(2);
|
||||
DefaultBorderThickness = new Thickness(_default_border_double);
|
||||
public static readonly double
|
||||
DefaultCornerRadius = 1,
|
||||
DefaultFontSize = 10,
|
||||
@@ -279,6 +282,9 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
GlyphRun abbrev_glyph_run = null;
|
||||
GlyphRun hedge_glyph_run = null;
|
||||
|
||||
base.OnRender(dc);
|
||||
// this is the main badge rectangle
|
||||
double border_thickness = BorderThickness.Top;
|
||||
@@ -286,7 +292,6 @@ namespace mbts.ui.controls {
|
||||
pen = pen.frozen();
|
||||
dc.DrawRoundedRectangle(Background, pen, new Rect(0, border_thickness, _gcr.layout_size.Width, _gcr.layout_size.Height), CornerRadius, CornerRadius);
|
||||
|
||||
|
||||
if (HasMultipleOrdersAtLevel) {
|
||||
_draw_triangle(
|
||||
new Point(_gcr.layout_size.Width - (2 * border_thickness - CornerRadius), _gcr.layout_size.Height - 5), // top right
|
||||
@@ -296,26 +301,7 @@ namespace mbts.ui.controls {
|
||||
);
|
||||
} else {
|
||||
if (IsHedgeOrder) {
|
||||
// I am leaving this little bit here in case we ever want to bring DrawLine back
|
||||
//(pen = new Pen(Brushes.Black, border_thickness)).Freeze();
|
||||
//// H on the right side of the badge
|
||||
//// left vertical line
|
||||
//dc.DrawLine(pen,
|
||||
// new Point(_gcr.layout_size.Width - (4 * border_thickness - CornerRadius), 0 + 2 * border_thickness),
|
||||
// new Point(_gcr.layout_size.Width - (4 * border_thickness - CornerRadius), _gcr.layout_size.Height)
|
||||
// );
|
||||
//// right vertical line
|
||||
//dc.DrawLine(pen,
|
||||
// new Point(_gcr.layout_size.Width - (2 * border_thickness - CornerRadius), 0 + 2 * border_thickness),
|
||||
// new Point(_gcr.layout_size.Width - (2 * border_thickness - CornerRadius), _gcr.layout_size.Height)
|
||||
//);
|
||||
//// horizontal line between
|
||||
//dc.DrawLine(pen,
|
||||
// new Point(_gcr.layout_size.Width - (4 * border_thickness - CornerRadius), (_gcr.layout_size.Height / 2) + 2),
|
||||
// new Point(_gcr.layout_size.Width - (2 * border_thickness - CornerRadius), (_gcr.layout_size.Height / 2) + 2)
|
||||
//);
|
||||
var a_glyph_context = _gcr.glyph_context;
|
||||
var a_glyph_run = a_glyph_context.get_glyph_run(
|
||||
hedge_glyph_run = _gcr.glyph_context.get_glyph_run(
|
||||
_hedge_indicator,
|
||||
FontSize,
|
||||
_gcr.layout_size.Width,
|
||||
@@ -324,12 +310,9 @@ namespace mbts.ui.controls {
|
||||
-1 * (2 * border_thickness + 1),
|
||||
VerticalAlignment.Center,
|
||||
0);
|
||||
dc.DrawGlyphRun(Brushes.Black, a_glyph_run);
|
||||
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Abbreviation)) {
|
||||
var a_glyph_context = _gcr.glyph_context;
|
||||
var a_glyph_run = a_glyph_context.get_glyph_run(
|
||||
abbrev_glyph_run = _gcr.glyph_context.get_glyph_run(
|
||||
Abbreviation,
|
||||
FontSize,
|
||||
_gcr.layout_size.Width,
|
||||
@@ -338,10 +321,8 @@ namespace mbts.ui.controls {
|
||||
2 * border_thickness + 1,
|
||||
VerticalAlignment.Center,
|
||||
0);
|
||||
dc.DrawGlyphRun(Brushes.Black, a_glyph_run);
|
||||
} else if (AlgoType != CustomOrderDefinition.CustomOneofCase.None) {
|
||||
var a_glyph_context = _gcr.glyph_context;
|
||||
var a_glyph_run = a_glyph_context.get_glyph_run(
|
||||
abbrev_glyph_run = _gcr.glyph_context.get_glyph_run(
|
||||
mbts.spark.utilities.OrderDataHelper.abbreviate_algo_type(AlgoType),
|
||||
FontSize,
|
||||
_gcr.layout_size.Width,
|
||||
@@ -350,17 +331,32 @@ namespace mbts.ui.controls {
|
||||
2 * border_thickness + 1,
|
||||
VerticalAlignment.Center,
|
||||
0);
|
||||
dc.DrawGlyphRun(Brushes.Black, a_glyph_run);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
var qty_g = _gcr.glyph_run;
|
||||
if (qty_g != null) {
|
||||
var qty_geom = qty_g.BuildGeometry();
|
||||
|
||||
var gr = _gcr.glyph_run;
|
||||
if (gr != null)
|
||||
dc.DrawGlyphRun(_foreground, gr);
|
||||
// maybe draw the algo abbreviation
|
||||
if (abbrev_glyph_run != null) {
|
||||
var ab_geom = abbrev_glyph_run.BuildGeometry();
|
||||
if (!qty_geom.Bounds.IntersectsWith(ab_geom.Bounds)) {
|
||||
dc.DrawGlyphRun(Brushes.Black, abbrev_glyph_run);
|
||||
}
|
||||
}
|
||||
|
||||
// we always draw the qty glyph run
|
||||
dc.DrawGlyphRun(_foreground, qty_g);
|
||||
|
||||
// maybe draw the hedge indicator
|
||||
if (hedge_glyph_run != null) {
|
||||
var hedge_geom = hedge_glyph_run.BuildGeometry();
|
||||
if (!qty_geom.Bounds.IntersectsWith(hedge_geom.Bounds)) {
|
||||
dc.DrawGlyphRun(Brushes.Black, hedge_glyph_run);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _draw_triangle(Point p1, Point p2, Point p3, Brush b) {
|
||||
if (!(p1 == _current_triangle_vertices.p1 && p2 == _current_triangle_vertices.p2 && p3 == _current_triangle_vertices.p3)) {
|
||||
|
||||
@@ -167,11 +167,11 @@ namespace mbts.spark.filters {
|
||||
_selected_items_set = new HashSet<TValue>(_equality_comparer);
|
||||
_items_set = new HashSet<TValue>(_equality_comparer);
|
||||
_unsorted_items = new ObservableCollection<TValue>();
|
||||
items = comparer != null ? new ObservableSortedCollection<TValue>(_unsorted_items, comparer) : _unsorted_items;
|
||||
items = comparer != null ? ObservableAutoWrapper.create_sorted_wrapper(_unsorted_items, comparer) : ObservableAutoWrapper.create_read_only_wrapper(_unsorted_items);
|
||||
selected_items.CollectionChanged += _on_selected_items_CollectionChanged;
|
||||
}
|
||||
|
||||
public ObservableCollection<TValue> items { get; }
|
||||
public IReadOnlyObservableCollection<TValue> items { get; }
|
||||
public ObservableCollectionEx<TValue> selected_items { get; } = new ObservableCollectionEx<TValue>();
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
using mbts.collections;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.ui.controls
|
||||
{
|
||||
namespace mbts.ui.controls {
|
||||
public interface IDataGridExRow
|
||||
{
|
||||
object get_cell(object col_key);
|
||||
@@ -28,25 +22,6 @@ namespace mbts.ui.controls
|
||||
void update(TData data);
|
||||
}
|
||||
|
||||
public class DataGridExRowCollection<TRow> : ObservableSortedCollection<TRow>, IComparer<TRow> where TRow : IDataGridExRow
|
||||
{
|
||||
public DataGridExRowCollection(ObservableCollection<TRow> source) : base (source) {
|
||||
hierarchical_sorting = new ObservableCollection<Tuple<Comparison<TRow>, bool>>();
|
||||
hierarchical_sorting.CollectionChanged += (s, e) => sort();
|
||||
comparer = this;
|
||||
}
|
||||
|
||||
public ObservableCollection<Tuple<Comparison<TRow>, bool>> hierarchical_sorting { get; private set; }
|
||||
|
||||
public int Compare(TRow x, TRow y) {
|
||||
foreach(var tup in hierarchical_sorting) {
|
||||
int c = tup.Item1(x, y);
|
||||
if (c != 0) return tup.Item2 ? c : -c;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class DataGridExRow<TColKey> : ViewModelBase, IDataGridExRow<TColKey>, IDataGridExRow
|
||||
{
|
||||
protected DataGridExRow() { }
|
||||
|
||||
@@ -55,14 +55,6 @@
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:AlgoContentTemplateSelector.TemplateDropDownStyleKey}" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding editing_permissions, Converter={StaticResource CanEditStructureConverter}}" Value="{u:False}">
|
||||
<Setter Property="ToolTip" Value="Cannot change structure of template."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style TargetType="c:TickUpDownDouble" BasedOn="{StaticResource TickUpDownDoubleFlushTextImmediately}"/>
|
||||
<Style TargetType="c:TickUpDownInt" BasedOn="{StaticResource TickUpDownIntFlushTextImmediately}"/>
|
||||
<Style TargetType="c:TickUpDownUInt" BasedOn="{StaticResource TickUpDownUIntFlushTextImmediately}"/>
|
||||
@@ -695,7 +687,10 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">Tick Offset</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" >Tick Offset</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="How many ticks off the inside market to deploy the order, this guards against crossing the market inadventently" />
|
||||
</StackPanel>
|
||||
<c:TickUpDownUInt x:Name="PART_TickOffsetOnDeploy_TickUpDownUInt" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
@@ -913,7 +908,10 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on GLBX, this is the exchange provided iceberg" />
|
||||
</StackPanel>
|
||||
<c:TickUpDownNullableUInt x:Name="PART_MaxShowQuantity_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_show_quantity}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -952,7 +950,10 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on exchange, this is the BTS provided iceberg" />
|
||||
</StackPanel>
|
||||
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding show_quantity}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
@@ -1009,6 +1010,8 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
|
||||
</Grid>
|
||||
@@ -1028,9 +1031,16 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="0" >
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
|
||||
<c:HelpButton Margin="4,0,0,0" ToolTip="The quantity we want visible on the exchange" />
|
||||
</StackPanel>
|
||||
<c:TickUpDownUInt Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding exposed}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="1" >
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="No single order will have a quantity greater than this quantity (Clip Size)" />
|
||||
</StackPanel>
|
||||
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_order_qty}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
@@ -1376,8 +1386,7 @@
|
||||
SelectedItem="{Binding selected_algo}"
|
||||
CanEditStructure="{Binding DataContext.editing_permissions, Converter={StaticResource CanEditStructureConverter}, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
ContentTemplateSelector="{DynamicResource AlgoTemplateSelector}"
|
||||
OrderKind="{Binding order_kind}"
|
||||
OrderKindTemplate="{StaticResource OrderKindTemplate}"
|
||||
OrderKind="{Binding display_name}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
@@ -9,13 +9,11 @@ namespace mbts.ui.spark {
|
||||
class AlgoContentTemplateSelector : DataTemplateSelector {
|
||||
public const string
|
||||
DefinitionDropDownStyleKey = nameof(DefinitionDropDownStyleKey),
|
||||
TemplateDropDownStyleKey = nameof(TemplateDropDownStyleKey),
|
||||
TriggerTimeoutEnabledStateName = nameof(TriggerTimeoutEnabledStateName);
|
||||
|
||||
public static readonly IValueConverter
|
||||
AlgoDropDownStyleKeyConverter = ValueConverter.create(obj => {
|
||||
if (obj is AlgoDefinitionTemplateVM t) return TemplateDropDownStyleKey;
|
||||
if (obj is AlgoDefinitionVM d) return DefinitionDropDownStyleKey;
|
||||
if (obj is AlgoDefinitionVM) return DefinitionDropDownStyleKey;
|
||||
return null;
|
||||
}),
|
||||
DataContextConverter = ValueConverter.create(obj => obj is AlgoVMWrapper w ? w.value.algo_root : obj);
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace mbts.ui.spark.algo {
|
||||
|
||||
#region DropDownStyleKey
|
||||
public static readonly DependencyProperty DropDownStyleKeyProperty =
|
||||
WPFHelper.create_dp<object, AlgoPickerNodeView>(nameof(DropDownStyleKey), AlgoContentTemplateSelector.TemplateDropDownStyleKey);
|
||||
WPFHelper.create_dp<object, AlgoPickerNodeView>(nameof(DropDownStyleKey), AlgoContentTemplateSelector.DefinitionDropDownStyleKey);
|
||||
|
||||
public object DropDownStyleKey {
|
||||
get => GetValue(DropDownStyleKeyProperty);
|
||||
|
||||
@@ -76,59 +76,17 @@
|
||||
x:Name="PART_AlgoTemplateToolBar"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
MinWidth="230"
|
||||
ShowRenameDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey}}"
|
||||
ShowSaveDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey}}"
|
||||
ShowTemplatesDropDown="{DynamicResource {x:Static sp:AlgoStudioWindow.TemplatesEnabledKey}}"
|
||||
Definition="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
|
||||
EditableCollectionModel="{Binding algo_template_collection_model}"
|
||||
Padding="0,1,0,3"
|
||||
RemoveCommand="{Binding remove_template_cmd}"
|
||||
UndoCommand="{Binding undo_cmd}"
|
||||
RedoCommand="{Binding redo_cmd}"
|
||||
UndoDefinitionCommand="{Binding undo_definition_cmd}"
|
||||
RedoDefinitionCommand="{Binding redo_definition_cmd}"
|
||||
ShowUndoRedoButtons="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey}}"
|
||||
SetDefaultsCommand="{Binding set_default_algo_params_cmd}"
|
||||
RevertDefinitionCommand="{Binding revert_algo_params_cmd}"
|
||||
ShowRevertDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey}}"
|
||||
SaveCommand="{Binding save_template_cmd}"
|
||||
SaveDefinitionCommand="{Binding save_definition_cmd}"
|
||||
ToggleFavoriteCommand="{Binding toggle_favorite_cmd}"
|
||||
SelectedItem="{Binding selected_algo_template, Mode=TwoWay}"
|
||||
ItemContainerStyle="{StaticResource AlgoTemplateItemContainerStyle}"
|
||||
>
|
||||
<uag:AlgoTemplateToolBar.Resources>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
<Button
|
||||
x:Name="remove_button"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="2,0,0,0"
|
||||
Style="{StaticResource AlgoTemplateRemoveButtonStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Static sp:AlgoDefinitionTemplateVM.CustomType}">
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
</DataTemplate>
|
||||
</uag:AlgoTemplateToolBar.Resources>
|
||||
<uag:AlgoTemplateToolBar.CommandBindings>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginAddNewCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginAddNewExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRevertCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRevertExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRevertDefinitionCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRevertDefinitionExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRemoveCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRemoveExecuted}"/>
|
||||
</uag:AlgoTemplateToolBar.CommandBindings>
|
||||
</uag:AlgoTemplateToolBar>
|
||||
/>
|
||||
<ContentControl
|
||||
x:Name="StudioView"
|
||||
Grid.Row="1"
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<DataTemplate x:Key="AlgoTemplateHeaderContentTemplate" DataType="sp:AlgoDefinitionVM">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
@@ -30,9 +28,6 @@
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
<Style x:Key="AlgoTemplateItemContainerStyle" TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoMenuItem" TargetType="MenuItem">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
@@ -60,9 +55,6 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Header" Value="{Binding Definition, RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource AlgoTemplateHeaderContentTemplate}"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource AlgoTemplateItemContainerStyle}"/>
|
||||
<Setter Property="ItemsSource" Value="{Binding algo_templates_view}"/>
|
||||
<Setter Property="SelectedItem" Value="{Binding selected_algo_template, Mode=TwoWay}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="uag:AlgoTemplateToolBar">
|
||||
@@ -79,12 +71,6 @@
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.UndoRedo}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.UndoRedo}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Undo_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Redo_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_UndoDefinition_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
@@ -94,26 +80,6 @@
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.ToggleFavorite}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.ToggleFavorite}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.ToggleFavorite}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_IsFavorite_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RenameDefinition}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RenameDefinition}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RenameDefinition}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_RenameDefinition_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.SaveDefinition}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.SaveDefinition}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.SaveDefinition}}">
|
||||
@@ -125,8 +91,8 @@
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RevertDefinition}}">
|
||||
<VisualState Name="{x:Static Active,MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RevertDefinition}}"/>
|
||||
<VisualState Name="{x:Static Inactive,MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RevertDefinition}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RevertDefinition}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RevertDefinition}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_RevertDefinition_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}"/>
|
||||
@@ -134,41 +100,6 @@
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RenameTemplate}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RenameTemplate}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.RenameTemplate}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Rename_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.TemplatesDropDown}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.TemplatesDropDown}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested uag:AlgoTemplateToolBar.VisualStateNames.TemplatesDropDown}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Templates_ComboBox" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_AddNew_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Rename_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Save_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Undo_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Redo_MenuItem" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
@@ -181,93 +112,10 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter ContentSource="Header"/>
|
||||
<c:EditableTextBlock
|
||||
x:Name="PART_DefinitionName_EditableTextBlock"
|
||||
Grid.Column="1"
|
||||
Margin="4,0,0,0"
|
||||
MinWidth="100"
|
||||
Text="{Binding Definition.name, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<c:EditableTextBlock.CommandBindings>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.EndEditCommand}" Executed="{x:Static c:EditableTextBlock.EndEditExecuted}"/>
|
||||
</c:EditableTextBlock.CommandBindings>
|
||||
<c:EditableTextBlock.ElementStyle>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</c:EditableTextBlock.ElementStyle>
|
||||
</c:EditableTextBlock>
|
||||
<ComboBox
|
||||
x:Name="PART_Templates_ComboBox"
|
||||
Grid.Column="2"
|
||||
Margin="4,0,0,0"
|
||||
MinWidth="100"
|
||||
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
|
||||
IsEditable="{Binding IsEditing, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||
ItemContainerStyle="{TemplateBinding ItemContainerStyle}"
|
||||
SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
ToolTip="Choose Template"
|
||||
/>
|
||||
<Menu x:Name="PART_MenuRoot_Menu" Grid.Column="3" Background="Transparent" Visibility="Visible">
|
||||
<MenuItem
|
||||
x:Name="PART_AddNew_MenuItem"
|
||||
Command="{x:Static uag:AlgoTemplateToolBar.BeginAddNewCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
ToolTip="Add New Template"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.AddBoxes}"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_Rename_MenuItem"
|
||||
Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
Width="{x:Static sys:Double.NaN}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.Pencil}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding SelectedItem.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Save \'{0}\' As..'}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_RenameDefinition_MenuItem"
|
||||
Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
Width="{x:Static sys:Double.NaN}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.Pencil}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding Definition.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Save \'{0}\' As..'}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_Save_MenuItem"
|
||||
Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.Disk}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding SelectedItem.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Save \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_SaveDefinition_MenuItem"
|
||||
Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionCommand}"
|
||||
Command="{TemplateBinding SaveDefinitionCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
@@ -295,32 +143,6 @@
|
||||
</TextBlock>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_Undo_MenuItem"
|
||||
Command="{TemplateBinding UndoCommand}"
|
||||
HorizontalContentAlignment="Center"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.UndoCircular}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding SelectedItem.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Undo changes to \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_Redo_MenuItem"
|
||||
Command="{TemplateBinding RedoCommand}"
|
||||
HorizontalContentAlignment="Center"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.RedoCircular}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding SelectedItem.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Redo changes to \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_UndoDefinition_MenuItem"
|
||||
Command="{TemplateBinding UndoDefinitionCommand}"
|
||||
@@ -359,18 +181,6 @@
|
||||
<Image Source="{x:Static u:Icons.UndoCircular}"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_IsFavorite_MenuItem"
|
||||
u:Behavior.AutoRaiseCanExecuteChanged="True"
|
||||
Command="{TemplateBinding ToggleFavoriteCommand}"
|
||||
CommandParameter="{Binding ToggleFavoriteCommandParameter, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
ToolTip="{x:Static uag:AlgoTemplateToolBar.AddToFavoritesToolTip}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image x:Name="_favorite_image" Source="{u:Icon Star, Brush=Transparent, Pen={u:Pen Black, Thickness=3}}" Stretch="Uniform"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -378,36 +188,9 @@
|
||||
<Trigger Property="IsReadOnly" Value="True">
|
||||
<Setter TargetName="PART_MenuRoot_Menu" Property="Visibility" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="SelectedItem" Value="{x:Null}">
|
||||
<Setter TargetName="PART_Rename_MenuItem" Property="IsEnabled" Value="False"/>
|
||||
<Setter TargetName="PART_Undo_MenuItem" Property="ToolTip" Value="{x:Null}"/>
|
||||
<Setter TargetName="PART_Redo_MenuItem" Property="ToolTip" Value="{x:Null}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEdited" Value="True">
|
||||
<Setter TargetName="PART_Templates_ComboBox" Property="BorderBrush" Value="{u:BTSColor UI.input_dirty_border}"/>
|
||||
<Setter TargetName="PART_Save_MenuItem" Property="IsEnabled" Value="True"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEditing" Value="True">
|
||||
<Setter TargetName="PART_Templates_ComboBox" Property="SelectedItem" Value="{Binding EditingItemPlaceholder, RelativeSource={RelativeSource Self}, Mode=TwoWay}"/>
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding Definition.is_dirty, RelativeSource={RelativeSource Self}}" Value="{u:True}">
|
||||
<Setter TargetName="PART_SaveDefinition_MenuItem" Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding SelectedItem.is_favorite, RelativeSource={RelativeSource Self}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter TargetName="_favorite_image" Property="Source" Value="{u:Icon Star, Brush=Gold, Pen={u:Pen Black, Thickness=3}}"/>
|
||||
<Setter TargetName="PART_IsFavorite_MenuItem" Property="ToolTip" Value="{x:Static uag:AlgoTemplateToolBar.RemoveFromFavoritesToolTip}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding SelectedItem.is_custom_placeholder, RelativeSource={RelativeSource Self}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter TargetName="PART_Rename_MenuItem" Property="IsEnabled" Value="False"/>
|
||||
<Setter TargetName="PART_IsFavorite_MenuItem" Property="IsEnabled" Value="False"/>
|
||||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0"/>
|
||||
<Condition Binding="{Binding IsEditing, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="PART_Templates_ComboBox" Property="IsEnabled" Value="False"/>
|
||||
</MultiDataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark.algo {
|
||||
@@ -18,51 +9,6 @@ namespace mbts.ui.spark.algo {
|
||||
/// Interaction logic for AlgoTemplateToolBar.xaml
|
||||
/// </summary>
|
||||
class AlgoTemplateToolBar : HeaderedItemsControl {
|
||||
// The Favorites toggle button exists in 2 forms: as a MenuItem in AlgoTemplateToolBar, and as a ToggleButton in AlgoStudioView
|
||||
// The const strings below are used to store this text in 1 place.
|
||||
public const string
|
||||
AddToFavoritesToolTip = "Add to Favorites",
|
||||
RemoveFromFavoritesToolTip = "Remove from Favorites";
|
||||
|
||||
public static readonly IValueConverter FavoriteToggleButtonToolTipConverter =
|
||||
ValueConverter.create<bool, string>(is_checked => is_checked ? RemoveFromFavoritesToolTip : AddToFavoritesToolTip);
|
||||
|
||||
public static readonly RoutedUICommand
|
||||
BeginAddNewCommand = new RoutedUICommand("Add New Template", "Add New", typeof(AlgoTemplateToolBar)),
|
||||
BeginSaveCommand = new RoutedUICommand("Save Template", "Save", typeof(AlgoTemplateToolBar)),
|
||||
BeginSaveDefinitionCommand = new RoutedUICommand("Save Definition", "Save", typeof(AlgoTemplateToolBar)),
|
||||
BeginSaveAsCommand = new RoutedUICommand("Save Template As", "Save As", typeof(AlgoTemplateToolBar)),
|
||||
BeginSaveDefinitionAsCommand = new RoutedUICommand("Save Definition As", "Save As", typeof(AlgoTemplateToolBar)),
|
||||
BeginRevertCommand = new RoutedUICommand("Revert to Template", "Revert", typeof(AlgoTemplateToolBar)),
|
||||
BeginRevertDefinitionCommand = new RoutedUICommand("Revert to Definition", "Revert", typeof(AlgoTemplateToolBar)),
|
||||
BeginRedoCommand = new RoutedUICommand("Redo", "Redo", typeof(AlgoTemplateToolBar)),
|
||||
BeginUndoCommand = new RoutedUICommand("Undo", "Undo", typeof(AlgoTemplateToolBar)),
|
||||
BeginRedoDefinitionCommand = new RoutedUICommand("Redo Definition", "Redo Definition", typeof(AlgoTemplateToolBar)),
|
||||
BeginUndoDefinitionCommand = new RoutedUICommand("Undo Definition", "Undo Definition", typeof(AlgoTemplateToolBar)),
|
||||
BeginRemoveCommand = new RoutedUICommand("Delete Template", "Delete", typeof(AlgoTemplateToolBar)),
|
||||
BeginToggleFavoriteCommand = new RoutedUICommand("Toggle Favorite", "Toggle Favorite", typeof(AlgoTemplateToolBar));
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler
|
||||
BeginAddNewExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._add_new()),
|
||||
BeginSaveExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._save()),
|
||||
BeginSaveDefinitionExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._save_definition()),
|
||||
BeginSaveAsExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._save_as()),
|
||||
BeginSaveDefinitionAsExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._save_definition_as()),
|
||||
BeginRevertExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._revert()),
|
||||
BeginRevertDefinitionExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._revert_definition()),
|
||||
BeginRedoExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._redo()),
|
||||
BeginUndoExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._undo()),
|
||||
BeginRedoDefinitionExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._redo_definition()),
|
||||
BeginUndoDefinitionExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, _) => tb._undo_definition()),
|
||||
BeginRemoveExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, t) => tb._remove(t)),
|
||||
BeginToggleFavoriteExecuted = WPFHelper.create_cmd_executed<object, AlgoTemplateToolBar>((tb, param) => tb.ToggleFavoriteCommand.TryExecuteIfCan(param));
|
||||
|
||||
ComboBox _templates_combo;
|
||||
EditableTextBlock _definition_name_text;
|
||||
IDisposable _input_hooks;
|
||||
// this is a casted backing for 'Items'
|
||||
IEditableCollectionViewAddNewItem _editable_items;
|
||||
|
||||
static AlgoTemplateToolBar() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(AlgoTemplateToolBar), new FrameworkPropertyMetadata(typeof(AlgoTemplateToolBar)));
|
||||
|
||||
@@ -73,37 +19,6 @@ namespace mbts.ui.spark.algo {
|
||||
VisualStateNames.apply_all(this);
|
||||
}
|
||||
|
||||
#region ShowTemplatesDropDown
|
||||
public static readonly DependencyProperty ShowTemplatesDropDownProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(
|
||||
nameof(ShowTemplatesDropDown),
|
||||
(o, v) => {
|
||||
string dropdown_state, rename_state;
|
||||
if (v) {
|
||||
dropdown_state = VisualStateNames.TemplatesDropDown.Active;
|
||||
rename_state = VisualStateNames.RenameTemplate.Active;
|
||||
} else {
|
||||
dropdown_state = VisualStateNames.TemplatesDropDown.Inactive;
|
||||
rename_state = VisualStateNames.RenameTemplate.Inactive;
|
||||
}
|
||||
o._vsm.set_state(VisualStateNames.TemplatesDropDown.GroupName, dropdown_state);
|
||||
o._vsm.set_state(VisualStateNames.RenameTemplate.GroupName, rename_state);
|
||||
},
|
||||
coerce_require_persisted);
|
||||
|
||||
public bool ShowTemplatesDropDown { get => (bool)GetValue(ShowTemplatesDropDownProperty); set => SetValue(ShowTemplatesDropDownProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ShowToggleFavorite
|
||||
public static readonly DependencyProperty ShowToggleFavoriteProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(
|
||||
nameof(ShowToggleFavorite),
|
||||
(o, v) => VisualStateNames.ToggleFavorite.apply(o._vsm, v),
|
||||
coerce_require_persisted);
|
||||
|
||||
public bool ShowToggleFavorite { get => (bool)GetValue(ShowToggleFavoriteProperty); set => SetValue(ShowToggleFavoriteProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ShowDefinitionName
|
||||
public static readonly DependencyProperty ShowDefinitionNameProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(nameof(ShowDefinitionName));
|
||||
@@ -130,16 +45,6 @@ namespace mbts.ui.spark.algo {
|
||||
public bool ShowRevertDefinition { get => (bool)GetValue(ShowRevertDefinitionProperty); set => SetValue(ShowRevertDefinitionProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ShowRenameDefinition
|
||||
public static readonly DependencyProperty ShowRenameDefinitionProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(
|
||||
nameof(ShowRenameDefinition),
|
||||
(o, v) => VisualStateNames.RenameDefinition.apply(o._vsm, v),
|
||||
coerce_require_persisted);
|
||||
|
||||
public bool ShowRenameDefinition { get => (bool)GetValue(ShowRenameDefinitionProperty); set => SetValue(ShowRenameDefinitionProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ShowSaveDefinition
|
||||
public static readonly DependencyProperty ShowSaveDefinitionProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(
|
||||
@@ -154,7 +59,6 @@ namespace mbts.ui.spark.algo {
|
||||
public static readonly DependencyProperty DefinitionProperty =
|
||||
WPFHelper.create_dp<AlgoDefinitionVM, AlgoTemplateToolBar>(nameof(Definition),
|
||||
(o, v) => {
|
||||
o.CoerceValue(ShowRenameDefinitionProperty);
|
||||
o.CoerceValue(ShowSaveDefinitionProperty);
|
||||
//o.CoerceValue(ShowRevertDefinitionProperty);
|
||||
o.CoerceValue(ShowUndoRedoButtonsProperty);
|
||||
@@ -163,51 +67,6 @@ namespace mbts.ui.spark.algo {
|
||||
public AlgoDefinitionVM Definition { get => GetValue(DefinitionProperty) as AlgoDefinitionVM; set => SetValue(DefinitionProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region SelectedItem
|
||||
public static readonly DependencyProperty SelectedItemProperty =
|
||||
WPFHelper.create_dp<AlgoDefinitionTemplateVM, AlgoTemplateToolBar>(
|
||||
nameof(SelectedItem),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, oldval, newval) => {
|
||||
// TODO: consider an alternate solution to the problem here, subscribing to PropertyChanged for 1 property feels a little hackey.
|
||||
// We could make IsEdited a non-readonly dp, and just bind to SelectedItem.is_edited; however, this compromises the AlgoTemplateToolBar's public interface bc the concept this value represents is not ambiguous.
|
||||
// IsEdited always means 1 thing: the SelectedItem's (algo-template) order-definition is 'dirty' - different from the values currently in the OrderPropertiesEditor.
|
||||
// The reason this is useful:
|
||||
/// When the algo template becomes dirty (which is easy to tell via a dpchanged callback), we call <see cref="IEditableCollectionView.EditItem(object)"/>.
|
||||
/// Then, in any subsequent Save/SaveAs Executed callbacks, we check if an item is currently being edited (<see cref="IEditableCollectionView.IsEditingItem"/>).
|
||||
/// If that is true, we update the IAlgoTemplateItem's order-definition (<see cref="IEditableCollectionView.CommitEdit"/>).
|
||||
/// <seealso cref="IsEditedPropertyKey"/>
|
||||
if (oldval is INotifyPropertyChanged oldnpc) oldnpc.PropertyChanged -= o._on_SelectedItem_PropertyChanged;
|
||||
if (newval is INotifyPropertyChanged newnpc) newnpc.PropertyChanged += o._on_SelectedItem_PropertyChanged;
|
||||
o.CoerceValue(IsEditedProperty);
|
||||
});
|
||||
|
||||
void _on_SelectedItem_PropertyChanged(object sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(AlgoDefinitionTemplateVM.is_dirty))
|
||||
CoerceValue(IsEditedProperty);
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM SelectedItem { get => GetValue(SelectedItemProperty) as AlgoDefinitionTemplateVM; set => SetValue(SelectedItemProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region EditableCollectionModel
|
||||
public static readonly DependencyProperty EditableCollectionModelProperty =
|
||||
WPFHelper.create_dp<IEditableCollectionModel<AlgoDefinitionTemplateVM>, AlgoTemplateToolBar>(nameof(EditableCollectionModel));
|
||||
public IEditableCollectionModel<AlgoDefinitionTemplateVM> EditableCollectionModel { get => GetValue(EditableCollectionModelProperty) as IEditableCollectionModel<AlgoDefinitionTemplateVM>; set => SetValue(EditableCollectionModelProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AddNew
|
||||
public static readonly DependencyProperty AddNewCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(AddNewCommand));
|
||||
public ICommand AddNewCommand { get => GetValue(AddNewCommandProperty) as ICommand; set => SetValue(AddNewCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region Save
|
||||
public static readonly DependencyProperty SaveCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(SaveCommand));
|
||||
public ICommand SaveCommand { get => GetValue(SaveCommandProperty) as ICommand; set => SetValue(SaveCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region SaveDefinition
|
||||
public static readonly DependencyProperty SaveDefinitionCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(SaveDefinitionCommand));
|
||||
@@ -220,36 +79,12 @@ namespace mbts.ui.spark.algo {
|
||||
public ICommand SetDefaultsCommand { get => GetValue(SetDefaultsCommandProperty) as ICommand; set => SetValue(SetDefaultsCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region Revert
|
||||
public static readonly DependencyProperty RevertCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(RevertCommand));
|
||||
public ICommand RevertCommand { get => GetValue(RevertCommandProperty) as ICommand; set => SetValue(RevertCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region RevertDefinition
|
||||
public static readonly DependencyProperty RevertDefinitionCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(RevertDefinitionCommand));
|
||||
public ICommand RevertDefinitionCommand { get => GetValue(RevertDefinitionCommandProperty) as ICommand; set => SetValue(RevertDefinitionCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region Remove
|
||||
public static readonly DependencyProperty RemoveCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(RemoveCommand));
|
||||
public ICommand RemoveCommand { get => GetValue(RemoveCommandProperty) as ICommand; set => SetValue(RemoveCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region Undo
|
||||
public static readonly DependencyProperty UndoCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(UndoCommand));
|
||||
public ICommand UndoCommand { get => GetValue(UndoCommandProperty) as ICommand; set => SetValue(UndoCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region Redo
|
||||
public static readonly DependencyProperty RedoCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(RedoCommand));
|
||||
public ICommand RedoCommand { get => GetValue(RedoCommandProperty) as ICommand; set => SetValue(RedoCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region UndoDefinition
|
||||
public static readonly DependencyProperty UndoDefinitionCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(UndoDefinitionCommand));
|
||||
@@ -262,33 +97,6 @@ namespace mbts.ui.spark.algo {
|
||||
public ICommand RedoDefinitionCommand { get => GetValue(RedoDefinitionCommandProperty) as ICommand; set => SetValue(RedoDefinitionCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ToggleFavorite
|
||||
public static readonly DependencyProperty ToggleFavoriteCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(ToggleFavoriteCommand));
|
||||
public ICommand ToggleFavoriteCommand { get => GetValue(ToggleFavoriteCommandProperty) as ICommand; set => SetValue(ToggleFavoriteCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ToggleFavorite Parameter
|
||||
public static readonly DependencyProperty ToggleFavoriteCommandParameterProperty =
|
||||
WPFHelper.create_dp<object, AlgoTemplateToolBar>(nameof(ToggleFavoriteCommandParameter));
|
||||
public object ToggleFavoriteCommandParameter { get => GetValue(ToggleFavoriteCommandParameterProperty); set => SetValue(ToggleFavoriteCommandParameterProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region IsEdited
|
||||
/// <summary>
|
||||
/// This property indicates whether or not the order definition held by the <see cref="SelectedItem"/> is 'dirty' - different from the one that would be created by the OrderPropertiesEditor at this time.
|
||||
/// When it becomes dirty, we call <see cref="IEditableCollectionView.EditItem(object)"/>. This leads the viewmodel to take a snapshot of its current state,
|
||||
/// so in the event that another template is selected, or the values in the OrderPropertiesEditor are reverted, we can snap everything back into place by calling <see cref="IEditableCollectionView.CancelEdit"/>
|
||||
/// </summary>
|
||||
static readonly DependencyPropertyKey IsEditedPropertyKey =
|
||||
WPFHelper.create_ro_dp<bool, AlgoTemplateToolBar>(
|
||||
nameof(IsEdited),
|
||||
null,
|
||||
(o, v) => o.SelectedItem is AlgoDefinitionTemplateVM t && t.is_dirty && !t.is_custom_placeholder);
|
||||
internal static readonly DependencyProperty IsEditedProperty = IsEditedPropertyKey.DependencyProperty;
|
||||
internal bool IsEdited { get => (bool)GetValue(IsEditedProperty); private set => SetValue(IsEditedPropertyKey, value); }
|
||||
#endregion
|
||||
|
||||
#region IsEditing
|
||||
public static readonly DependencyProperty IsEditingProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(nameof(IsEditing));
|
||||
@@ -301,152 +109,14 @@ namespace mbts.ui.spark.algo {
|
||||
public bool IsReadOnly { get => (bool)GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); }
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
Disposable.dispose(ref _input_hooks);
|
||||
_templates_combo = this.assert_template_child<ComboBox>("PART_Templates_ComboBox");
|
||||
_definition_name_text = this.assert_template_child<EditableTextBlock>("PART_DefinitionName_EditableTextBlock");
|
||||
_input_hooks = Disposable.Create(
|
||||
_definition_name_text.SubscribePreviewKeyDown((s, e) => {
|
||||
switch (e.Key) {
|
||||
case Key.Escape:
|
||||
if (_definition_name_text.IsEditing)
|
||||
_definition_name_text.abort();
|
||||
break;
|
||||
}
|
||||
}),
|
||||
_templates_combo.SubscribePreviewKeyDown((s, e) => {
|
||||
switch (e.Key) {
|
||||
case Key.Enter:
|
||||
Keyboard.ClearFocus();
|
||||
_commit();
|
||||
break;
|
||||
case Key.Escape:
|
||||
_cancel();
|
||||
Keyboard.ClearFocus();
|
||||
SetCurrentValue(IsEditingProperty, false);
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) {
|
||||
base.OnItemsChanged(e);
|
||||
_editable_items = Items;
|
||||
}
|
||||
|
||||
static bool coerce_require_persisted(AlgoTemplateToolBar d, bool value) =>
|
||||
value && d.Definition is AlgoDefinitionVM vm && vm.key.mode == AlgoKey.Mode.Persisted;
|
||||
|
||||
#region Transactional
|
||||
void _add_new() {
|
||||
_prepare_edit();
|
||||
var model = EditableCollectionModel;
|
||||
if (model != null && model.can_add_new)
|
||||
_editable_items.AddNewItem(model.new_item_factory());
|
||||
}
|
||||
|
||||
void _cancel() => _editable_items.CancelAny();
|
||||
|
||||
void _commit(bool execute_save = false) {
|
||||
if (_editable_items.IsEditingItem) {
|
||||
var editing_item = (AlgoDefinitionTemplateVM)_editable_items.CurrentEditItem;
|
||||
editing_item.name = _templates_combo.Text;
|
||||
_editable_items.CommitEdit();
|
||||
SetCurrentValue(SelectedItemProperty, editing_item);
|
||||
if (execute_save)
|
||||
SaveCommand.TryExecuteIfCan(editing_item);
|
||||
SetCurrentValue(IsEditingProperty, false);
|
||||
} else if (_editable_items.IsAddingNew) {
|
||||
var new_item = (AlgoDefinitionTemplateVM)_editable_items.CurrentAddItem;
|
||||
new_item.name = _templates_combo.Text;
|
||||
_editable_items.CommitNew();
|
||||
SetCurrentValue(SelectedItemProperty, new_item);
|
||||
if (execute_save)
|
||||
SaveCommand.TryExecuteIfCan(new_item);
|
||||
SetCurrentValue(IsEditingProperty, false);
|
||||
} else {
|
||||
SaveCommand.TryExecuteIfCan(SelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
void _revert() => RevertCommand.TryExecuteIfCan(null);
|
||||
void _revert_definition() => RevertDefinitionCommand.TryExecuteIfCan(null);
|
||||
|
||||
void _redo() => _redo(SelectedItem);
|
||||
void _undo() => _undo(SelectedItem);
|
||||
void _redo_definition() => _redo(Definition);
|
||||
void _undo_definition() => _undo(Definition);
|
||||
|
||||
void _redo(IAlgoStudioContentVM c) {
|
||||
if (c != null && c.can_redo)
|
||||
c.redo();
|
||||
}
|
||||
void _undo(IAlgoStudioContentVM c) {
|
||||
if (c != null && c.can_undo)
|
||||
c.undo();
|
||||
}
|
||||
|
||||
void _remove(object template) {
|
||||
_editable_items?.Remove(template);
|
||||
RemoveCommand.TryExecuteIfCan(template);
|
||||
}
|
||||
|
||||
void _save() {
|
||||
if (SelectedItem == null) {
|
||||
_add_new();
|
||||
} else _commit(true);
|
||||
}
|
||||
|
||||
void _save_definition() {
|
||||
if (Definition is AlgoDefinitionVM d && d.is_dirty) {
|
||||
_definition_name_text?.end_edit();
|
||||
SaveDefinitionCommand?.TryExecuteIfCan(d);
|
||||
}
|
||||
}
|
||||
|
||||
void _save_as() {
|
||||
if (SelectedItem == null) {
|
||||
_add_new();
|
||||
} else {
|
||||
_prepare_edit();
|
||||
_editable_items.EditItem(SelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
void _save_definition_as() {
|
||||
_definition_name_text?.begin_edit();
|
||||
}
|
||||
|
||||
bool _focus() {
|
||||
if (_templates_combo == null || !_templates_combo.IsEnabled) return false;
|
||||
var textbox = _templates_combo.descendants_of_type<TextBox>().FirstOrDefault();
|
||||
if (textbox == null) return false;
|
||||
FocusManager.SetFocusedElement(this, textbox);
|
||||
Focus();
|
||||
Keyboard.Focus(textbox);
|
||||
return true;
|
||||
}
|
||||
|
||||
void _prepare_edit() {
|
||||
_editable_items.CancelAny();
|
||||
SetCurrentValue(IsEditingProperty, true);
|
||||
// This is a hack ¯\_(ツ)_/¯ but it is effective and simple.
|
||||
// The correct/non-hack way to achieve this behavior most likely requires re-templating the ComboBox - a lot of work.
|
||||
/// The goal: auto-focus the textbox inside the ComboBox.
|
||||
/// The dilemma: the textbox does not exist until the ComboBox is rendered again (since we just changed ComboBox.IsEditing to true in the previous line, this has not happened yet).
|
||||
WPFHelper.push_frame_until(() => _focus());
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static void apply_all(AlgoTemplateToolBar toolbar) {
|
||||
UndoRedo.apply(toolbar._vsm, toolbar.ShowUndoRedoButtons);
|
||||
RevertDefinition.apply(toolbar, toolbar.ShowRevertDefinition);
|
||||
RenameDefinition.apply(toolbar._vsm, toolbar.ShowRenameDefinition);
|
||||
SaveDefinition.apply(toolbar._vsm, toolbar.ShowSaveDefinition);
|
||||
TemplatesDropDown.apply(toolbar._vsm, toolbar.ShowTemplatesDropDown);
|
||||
RenameTemplate.apply(toolbar._vsm, toolbar.ShowTemplatesDropDown); // there is no corresponding 'CanUserRenameTemplate' property at the moment, for now it just refers to ShowTemplates.
|
||||
ToggleFavorite.apply(toolbar._vsm, toolbar.ShowToggleFavorite);
|
||||
}
|
||||
public static class UndoRedo {
|
||||
public const string GroupName = nameof(UndoRedo);
|
||||
@@ -466,15 +136,6 @@ namespace mbts.ui.spark.algo {
|
||||
public static void apply(AlgoTemplateToolBar owner, bool active) =>
|
||||
owner.Dispatcher.InvokeAsync(() => owner._vsm.set_state(GroupName, active ? Active : Inactive));
|
||||
}
|
||||
public static class RenameDefinition {
|
||||
public const string GroupName = nameof(RenameDefinition);
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
|
||||
public static void apply(VisualStateModel vsm, bool active) =>
|
||||
vsm.set_state(GroupName, active ? Active : Inactive);
|
||||
}
|
||||
public static class SaveDefinition {
|
||||
public const string GroupName = nameof(SaveDefinition);
|
||||
public static readonly string
|
||||
@@ -484,33 +145,6 @@ namespace mbts.ui.spark.algo {
|
||||
public static void apply(VisualStateModel vsm, bool active) =>
|
||||
vsm.set_state(GroupName, active ? Active : Inactive);
|
||||
}
|
||||
public static class TemplatesDropDown {
|
||||
public const string GroupName = nameof(TemplatesDropDown);
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
|
||||
public static void apply(VisualStateModel vsm, bool active) =>
|
||||
vsm.set_state(GroupName, active ? Active : Inactive);
|
||||
}
|
||||
public static class RenameTemplate {
|
||||
public const string GroupName = nameof(RenameTemplate);
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
|
||||
public static void apply(VisualStateModel vsm, bool active) =>
|
||||
vsm.set_state(GroupName, active ? Active : Inactive);
|
||||
}
|
||||
public static class ToggleFavorite {
|
||||
public const string GroupName = nameof(ToggleFavorite);
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
|
||||
public static void apply(VisualStateModel vsm, bool active) =>
|
||||
vsm.set_state(GroupName, active ? Active : Inactive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<!--Control colors.-->
|
||||
<Color x:Key="ControlLightColor">White</Color>
|
||||
<SolidColorBrush x:Key="ControlLightBrush" Color="{StaticResource ControlLightColor}"/>
|
||||
|
||||
<!--Border colors-->
|
||||
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
|
||||
<Color x:Key="BorderMediumColor">#FF888888</Color>
|
||||
<Color x:Key="BorderDarkColor">#FF444444</Color>
|
||||
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
|
||||
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>
|
||||
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
|
||||
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
|
||||
<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
|
||||
|
||||
<SolidColorBrush x:Key="BorderLightBrush" Color="{StaticResource BorderLightColor}"/>
|
||||
<SolidColorBrush x:Key="BorderMediumBrush" Color="{StaticResource BorderMediumColor}"/>
|
||||
<SolidColorBrush x:Key="BorderDarkBrush" Color="{StaticResource BorderDarkColor}"/>
|
||||
<SolidColorBrush x:Key="PressedBorderLightBrush" Color="{StaticResource PressedBorderLightColor}"/>
|
||||
<SolidColorBrush x:Key="PressedBorderDarkBrush" Color="{StaticResource PressedBorderDarkColor}"/>
|
||||
<SolidColorBrush x:Key="DisabledBorderLightBrush" Color="{StaticResource DisabledBorderLightColor}"/>
|
||||
<SolidColorBrush x:Key="DisabledBorderDarkBrush" Color="{StaticResource DisabledBorderDarkColor}"/>
|
||||
<SolidColorBrush x:Key="DefaultBorderBrushDarkBrush" Color="{StaticResource DefaultBorderBrushDarkColor}"/>
|
||||
|
||||
<!--ExpanderToggle Colors-->
|
||||
<Color x:Key="Expander.Collapsed.Static.Fill.Color">#FFFFFFFF</Color>
|
||||
<Color x:Key="Expander.Collapsed.Static.Stroke.Color">#FF818181</Color>
|
||||
<Color x:Key="Expander.Collapsed.MouseOver.Fill.Color">#FFCCEEFB</Color>
|
||||
<Color x:Key="Expander.Collapsed.MouseOver.Stroke.Color">#FF27C7F7</Color>
|
||||
<Color x:Key="Expander.Expanded.Static.Fill.Color">#FF595959</Color>
|
||||
<Color x:Key="Expander.Expanded.Static.Stroke.Color">#FF262626</Color>
|
||||
<Color x:Key="Expander.Expanded.MouseOver.Fill.Color">#FF82DFFB</Color>
|
||||
<Color x:Key="Expander.Expanded.MouseOver.Stroke.Color">#FF1CC4F7</Color>
|
||||
|
||||
<SolidColorBrush x:Key="Expander.Collapsed.Static.Fill.Brush" Color="{StaticResource Expander.Collapsed.Static.Fill.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Collapsed.Static.Stroke.Brush" Color="{StaticResource Expander.Collapsed.Static.Stroke.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Collapsed.MouseOver.Fill.Brush" Color="{StaticResource Expander.Collapsed.MouseOver.Fill.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Collapsed.MouseOver.Stroke.Brush" Color="{StaticResource Expander.Collapsed.MouseOver.Stroke.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Expanded.Static.Fill.Brush" Color="{StaticResource Expander.Expanded.Static.Fill.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Expanded.Static.Stroke.Brush" Color="{StaticResource Expander.Expanded.Static.Stroke.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Expanded.MouseOver.Fill.Brush" Color="{StaticResource Expander.Expanded.MouseOver.Fill.Color}"/>
|
||||
<SolidColorBrush x:Key="Expander.Expanded.MouseOver.Stroke.Brush" Color="{StaticResource Expander.Expanded.MouseOver.Stroke.Color}"/>
|
||||
|
||||
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Expander.Collapsed.Static.Stroke.Brush}"/>
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="Padding" Value="5"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<Path
|
||||
x:Name="arrow"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Data="M0,0 L0,6 L6,0"
|
||||
Fill="{TemplateBinding Background}"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
StrokeThickness="{Binding BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
>
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterX="3" CenterY="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="arrow" Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterX="3" CenterY="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{StaticResource Expander.Expanded.MouseOver.Fill.Brush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Expander.Expanded.MouseOver.Stroke.Brush}"/>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="False"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{StaticResource Expander.Collapsed.MouseOver.Fill.Brush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Expander.Collapsed.MouseOver.Stroke.Brush}"/>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="False"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{StaticResource Expander.Expanded.Static.Fill.Brush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Expander.Expanded.Static.Stroke.Brush}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoTreeViewItemContainerStyleBase" TargetType="TreeViewItem">
|
||||
<Setter Property="uag:AlgoTemplateTreeView.NodeLevel" Value="{Binding Path='(uag:AlgoTemplateTreeView.NodeLevel)', RelativeSource={RelativeSource AncestorType=TreeViewItem}, Converter={x:Static uag:AlgoTemplateTreeView.NodeLevelConverter}, FallbackValue={u:Int 0}}"/>
|
||||
<Setter Property="uag:AlgoTemplateTreeView.ItemsMargin">
|
||||
<Setter.Value>
|
||||
<Thickness Left="8" Top="1" Right="1" Bottom="1"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Padding">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static uag:AlgoTemplateTreeView.ItemsMarginConverter}">
|
||||
<Binding Path="(uag:AlgoTemplateTreeView.ItemsMargin)" RelativeSource="{RelativeSource Self}"/>
|
||||
<Binding Path="(uag:AlgoTemplateTreeView.NodeLevel)" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TreeViewItem}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="ExpansionStates">
|
||||
<VisualState x:Name="Expanded">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="ItemsHost">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Collapsed" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border x:Name="Bd"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="19" Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton x:Name="Expander"
|
||||
Style="{StaticResource ExpandCollapseToggleStyle}"
|
||||
ClickMode="Press"
|
||||
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
<ContentPresenter x:Name="PART_Header" Grid.Column="1" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ItemsPresenter x:Name="ItemsHost"
|
||||
Grid.Row="1"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="HasItems" Value="False">
|
||||
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="HasHeader" Value="False" />
|
||||
<Condition Property="Width" Value="Auto" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="PART_Header" Property="MinWidth" Value="75" />
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="HasHeader" Value="False" />
|
||||
<Condition Property="Height" Value="Auto" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="PART_Header" Property="MinHeight" Value="19" />
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsSelected" Value="True" />
|
||||
<Condition Property="IsFocused" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{StaticResource ListBoxItem.Selected.Active.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource ListBoxItem.Selected.Active.Border}"/>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsSelected" Value="True" />
|
||||
<Condition Property="IsFocused" Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBoxItem.Selected.Inactive.Background}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBoxItem.Selected.Inactive.Border}"/>
|
||||
</MultiTrigger>
|
||||
<Trigger SourceName="Bd" Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBoxItem.MouseOver.Background}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBoxItem.MouseOver.Border}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoTemplateTreeViewItemContainerStyle" TargetType="TreeViewItem" BasedOn="{StaticResource AlgoTreeViewItemContainerStyleBase}">
|
||||
<Setter Property="u:AttachedCommands.DoubleClick" Value="{Binding set_as_default_cmd}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoDefinitionTreeViewItemContainerStyle" TargetType="TreeViewItem" BasedOn="{StaticResource AlgoTreeViewItemContainerStyleBase}">
|
||||
<Setter Property="IsExpanded" Value="{Binding is_expanded}"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource AlgoTemplateTreeViewItemContainerStyle}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoTemplateTreeViewStyle" TargetType="TreeView">
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="Background" Value="{StaticResource ControlLightBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderMediumBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource AlgoDefinitionTreeViewItemContainerStyle}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TreeView">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<ScrollViewer Focusable="False" CanContentScroll="False" Padding="0,4">
|
||||
<ItemsPresenter/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,33 +0,0 @@
|
||||
using mbts.ui.converters;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.spark.algo {
|
||||
static class AlgoTemplateTreeView {
|
||||
public static readonly IMultiValueConverter ItemsMarginConverter =
|
||||
ValueConverter.create<Thickness, int, Thickness>((margin, level) => new Thickness(level * margin.Left, margin.Top, margin.Right, margin.Bottom));
|
||||
|
||||
public static readonly IValueConverter NodeLevelConverter =
|
||||
ValueConverter.create<int, int>(pnode => pnode + 1);
|
||||
|
||||
public static readonly DependencyProperty ItemsMarginProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"ItemsMargin",
|
||||
typeof(Thickness),
|
||||
typeof(AlgoTemplateTreeView),
|
||||
new PropertyMetadata(new Thickness(1)));
|
||||
|
||||
public static Thickness GetItemsMargin(DependencyObject d) => (Thickness)d.GetValue(ItemsMarginProperty);
|
||||
public static void SetItemsMargin(DependencyObject d, Thickness value) => d.SetValue(ItemsMarginProperty, value);
|
||||
|
||||
public static readonly DependencyProperty NodeLevelProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"NodeLevel",
|
||||
typeof(int),
|
||||
typeof(AlgoTemplateTreeView),
|
||||
new PropertyMetadata(0));
|
||||
|
||||
public static int GetNodeLevel(DependencyObject d) => (int)d.GetValue(NodeLevelProperty);
|
||||
public static void SetNodeLevel(DependencyObject d, int value) => d.SetValue(NodeLevelProperty, value);
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,6 @@
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Style x:Key="AlgoTemplateRemoveButtonStyle" TargetType="Button" BasedOn="{StaticResource RoundCloseButton}">
|
||||
<Setter Property="Command" Value="{x:Static uag:AlgoTemplateToolBar.BeginRemoveCommand}"/>
|
||||
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=uag:AlgoTemplateToolBar}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
<Setter Property="Visibility" Value="{Binding Visibility, RelativeSource={RelativeSource AncestorType=ComboBoxItem}, FallbackValue={u:Visibility Collapsed}}"/>
|
||||
</Style>
|
||||
<Style TargetType="uag:OrderDetailsPopupControl">
|
||||
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource ComboBox.Static.Border}"/>
|
||||
@@ -110,58 +104,17 @@
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
MinWidth="230"
|
||||
IsReadOnly="{TemplateBinding IsAlgoTemplateToolBarReadOnly}"
|
||||
ShowRenameDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey}}"
|
||||
ShowSaveDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey}}"
|
||||
ShowTemplatesDropDown="{DynamicResource {x:Static sp:AlgoStudioWindow.TemplatesEnabledKey}}"
|
||||
Definition="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=uag:OrderDetailsPopupControl}}"
|
||||
EditableCollectionModel="{Binding algo_template_collection_model}"
|
||||
Padding="0,1,0,3"
|
||||
RemoveCommand="{Binding remove_template_cmd}"
|
||||
UndoCommand="{Binding undo_cmd}"
|
||||
RedoCommand="{Binding redo_cmd}"
|
||||
UndoDefinitionCommand="{Binding undo_definition_cmd}"
|
||||
RedoDefinitionCommand="{Binding redo_definition_cmd}"
|
||||
ShowUndoRedoButtons="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey}}"
|
||||
SetDefaultsCommand="{Binding set_default_algo_params_cmd}"
|
||||
RevertDefinitionCommand="{Binding revert_algo_params_cmd}"
|
||||
ShowRevertDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey}}"
|
||||
SaveCommand="{Binding save_template_cmd}"
|
||||
SaveDefinitionCommand="{Binding save_definition_cmd}"
|
||||
ToggleFavoriteCommand="{Binding toggle_favorite_cmd}"
|
||||
SelectedItem="{Binding selected_algo_template, Mode=TwoWay}"
|
||||
>
|
||||
<uag:AlgoTemplateToolBar.Resources>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
<Button
|
||||
x:Name="remove_button"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="2,0,0,0"
|
||||
Style="{StaticResource AlgoTemplateRemoveButtonStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Static sp:AlgoDefinitionTemplateVM.CustomType}">
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
</DataTemplate>
|
||||
</uag:AlgoTemplateToolBar.Resources>
|
||||
<uag:AlgoTemplateToolBar.CommandBindings>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginAddNewCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginAddNewExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveAsExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginSaveDefinitionAsExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRevertCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRevertExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRevertDefinitionCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRevertDefinitionExecuted}"/>
|
||||
<CommandBinding Command="{x:Static uag:AlgoTemplateToolBar.BeginRemoveCommand}" Executed="{x:Static uag:AlgoTemplateToolBar.BeginRemoveExecuted}"/>
|
||||
</uag:AlgoTemplateToolBar.CommandBindings>
|
||||
</uag:AlgoTemplateToolBar>
|
||||
/>
|
||||
<ContentControl
|
||||
x:Name="StudioView"
|
||||
Grid.Row="1"
|
||||
|
||||
@@ -61,6 +61,26 @@ namespace mbts.ui.extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable ConnectDataContext<T>(this FrameworkElement element, Action<T> connect, Action<T> disconnect) =>
|
||||
ConnectDataContext<T>(element, vm => {
|
||||
connect(vm);
|
||||
return Disposable.Create(() => disconnect(vm));
|
||||
});
|
||||
|
||||
public static IDisposable ConnectDataContext<T>(this FrameworkElement element, Func<T, IDisposable> connect) {
|
||||
var connection = element.DataContext is T vm ? connect(vm) : null;
|
||||
element.DataContextChanged += on_data_context_changed;
|
||||
return Disposable.Create(dispose);
|
||||
//
|
||||
void on_data_context_changed(object sender, DependencyPropertyChangedEventArgs e) =>
|
||||
Disposable.replace(ref connection, e.NewValue is T new_vm ? connect(new_vm) : null);
|
||||
|
||||
void dispose() {
|
||||
Disposable.dispose(ref connection);
|
||||
element.DataContextChanged -= on_data_context_changed;
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<bool> ObserveIsChecked(this ToggleButton toggle_button) =>
|
||||
Observable.Create<bool>(o => {
|
||||
toggle_button.Checked += on_checked;
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
xmlns:autocomplete="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
x:Name="_login_view"
|
||||
Width="460"
|
||||
Height="254"
|
||||
Width="520"
|
||||
Height="230"
|
||||
WindowStyle="None"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
@@ -27,7 +27,7 @@
|
||||
</ui:StateManager.Groups>
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<sys:Double x:Key="LoginControlPaddingLeft">40</sys:Double>
|
||||
<sys:Double x:Key="LoginControlPaddingLeft">10</sys:Double>
|
||||
<sys:Double x:Key="LoginControlPaddingTop">10</sys:Double>
|
||||
<sys:Double x:Key="LoginControlPaddingRight">22</sys:Double>
|
||||
<converters:BooleanToVisibilityConverter x:Key="InverseBoolToVisibility" VisibilityForFalse="Visible" VisibilityForTrue="Collapsed"/>
|
||||
@@ -206,7 +206,7 @@
|
||||
x:Name="_error_control"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Visibility="Hidden"
|
||||
Opacity="0"
|
||||
>
|
||||
@@ -251,10 +251,10 @@
|
||||
<c:DialogSkirt x:Name="_dialog_skirt" Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button x:Name="_login_button" Width="60" Margin="10" IsDefault="True" IsHitTestVisible="True" Command="{Binding LoginCommand}">
|
||||
<Button x:Name="_login_button" Width="60" Margin="10,0" IsDefault="True" IsHitTestVisible="True" Command="{Binding LoginCommand}">
|
||||
Login
|
||||
</Button>
|
||||
<Button x:Name="_cancel_button" Width="60" Margin="10" IsCancel="True" IsHitTestVisible="True" Command="{Binding CancelCommand}">
|
||||
<Button x:Name="_cancel_button" Width="60" Margin="10,0" IsCancel="True" IsHitTestVisible="True" Command="{Binding CancelCommand}">
|
||||
Cancel
|
||||
</Button>
|
||||
</x:Array>
|
||||
|
||||
@@ -141,11 +141,8 @@ namespace mbts.ui {
|
||||
return null;
|
||||
_ensure_current();
|
||||
Point pt = _vcr.content_area.BottomLeft;
|
||||
if (_glyph_run == null) {
|
||||
if (_glyph_run == null || _glyph_run.BaselineOrigin != pt)
|
||||
_glyph_run = _glyph_context.from_arranged(_font_size, pt, _glyph_indices, _advance_widths, _glyph_offsets);
|
||||
} else if (_glyph_run.BaselineOrigin != pt) {
|
||||
_glyph_run.BaselineOrigin = pt;
|
||||
}
|
||||
return _glyph_run;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,4 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("spark_core")]
|
||||
[assembly: InternalsVisibleTo("sparkcmd")]
|
||||
[assembly: InternalsVisibleTo("spark_test")]
|
||||
[assembly: InternalsVisibleTo("spark_excel")]
|
||||
|
||||
@@ -719,45 +719,50 @@ namespace bts.api.core {
|
||||
|
||||
// Constructs a new dictionary so not as cheap as Select on the Update<> type
|
||||
public static IObservable<TableUpdate<T2, TKey>>
|
||||
Select<T1, T2, TKey>(this IObservable<TableUpdate<T1, TKey>> src, Func<T1, T2> map, Func<T2, TKey> key) where TKey : IEquatable<TKey> {
|
||||
var cache = ImmutableDictionary<TKey, T2>.Empty;
|
||||
return src.Select(update => {
|
||||
var added = update.added.Select(map);
|
||||
var removed = update.removed.Select(map);
|
||||
var changes = update.changes.Select(ch => new Change<T2>(map(ch.Old), map(ch.New)));
|
||||
var builder = cache.ToBuilder();
|
||||
builder.RemoveRange(removed.Select(key));
|
||||
builder.AddRange(added.Select(t => new KeyValuePair<TKey,T2>(key(t), t)));
|
||||
foreach (var (o,n) in changes) {
|
||||
Debug.Assert(key(o).Equals(key(n)));
|
||||
builder[key(o)] = n;
|
||||
}
|
||||
cache = builder.ToImmutable();
|
||||
Select<T1, T2, TKey>(this IObservable<TableUpdate<T1, TKey>> src, Func<T1, T2> map, Func<T2, TKey> key) where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<T2, TKey>>(observer => {
|
||||
var cache = ImmutableDictionary<TKey, T2>.Empty;
|
||||
return src.Subscribe(update => {
|
||||
var added = update.added.Select(map);
|
||||
var removed = update.removed.Select(map);
|
||||
var changes = update.changes.Select(ch => new Change<T2>(map(ch.Old), map(ch.New)));
|
||||
var builder = cache.ToBuilder();
|
||||
builder.RemoveRange(removed.Select(key));
|
||||
builder.AddRange(added.Select(t => new KeyValuePair<TKey, T2>(key(t), t)));
|
||||
foreach (var (o, n) in changes) {
|
||||
Debug.Assert(key(o).Equals(key(n)));
|
||||
builder[key(o)] = n;
|
||||
}
|
||||
cache = builder.ToImmutable();
|
||||
|
||||
return new TableUpdate<T2, TKey>(cache, added, changes, removed, key);
|
||||
observer.OnNext(new TableUpdate<T2, TKey>(cache, added, changes, removed, key));
|
||||
},
|
||||
err => { observer.OnError(err); },
|
||||
() => { observer.OnCompleted(); });
|
||||
});
|
||||
}
|
||||
|
||||
// Constructs a new dictionary so not as cheap as Select on the Update<> type
|
||||
public static IObservable<TableUpdate<T2, TKey2>>
|
||||
SelectKeyValue<T1, T2, TKey, TKey2>(this IObservable<TableUpdate<T1, TKey>> src, Func<T1, T2> map, Func<T2, TKey2> key) where TKey : IEquatable<TKey> where TKey2 : IEquatable<TKey2> {
|
||||
var cache = ImmutableDictionary<TKey2, T2>.Empty;
|
||||
return src.Select(update => {
|
||||
var added = update.added.Select(map);
|
||||
var removed = update.removed.Select(map);
|
||||
var changes = update.changes.Select(ch => new Change<T2>(map(ch.Old), map(ch.New)));
|
||||
var builder = cache.ToBuilder();
|
||||
builder.RemoveRange(removed.Select(key));
|
||||
builder.AddRange(added.Select(t => new KeyValuePair<TKey2,T2>(key(t), t)));
|
||||
foreach (var (o,n) in changes) {
|
||||
Debug.Assert(key(o).Equals(key(n)));
|
||||
builder[key(o)] = n;
|
||||
}
|
||||
cache = builder.ToImmutable();
|
||||
|
||||
return new TableUpdate<T2, TKey2>(cache, added, changes, removed, key);
|
||||
SelectKeyValue<T1, T2, TKey, TKey2>(this IObservable<TableUpdate<T1, TKey>> src, Func<T1, T2> map, Func<T2, TKey2> key) where TKey : IEquatable<TKey> where TKey2 : IEquatable<TKey2> =>
|
||||
Observable.Create<TableUpdate<T2, TKey2>>(observer => {
|
||||
var cache = ImmutableDictionary<TKey2, T2>.Empty;
|
||||
return src.Subscribe(update => {
|
||||
var added = update.added.Select(map);
|
||||
var removed = update.removed.Select(map);
|
||||
var changes = update.changes.Select(ch => new Change<T2>(map(ch.Old), map(ch.New)));
|
||||
var builder = cache.ToBuilder();
|
||||
builder.RemoveRange(removed.Select(key));
|
||||
builder.AddRange(added.Select(t => new KeyValuePair<TKey2, T2>(key(t), t)));
|
||||
foreach (var (o, n) in changes) {
|
||||
Debug.Assert(key(o).Equals(key(n)));
|
||||
builder[key(o)] = n;
|
||||
}
|
||||
cache = builder.ToImmutable();
|
||||
observer.OnNext(new TableUpdate<T2, TKey2>(cache, added, changes, removed, key));
|
||||
},
|
||||
err => { observer.OnError(err); },
|
||||
() => { observer.OnCompleted(); });
|
||||
});
|
||||
}
|
||||
|
||||
public static IObservable<TableUpdate<(TLeft left, TRight right), TKey>> LeftJoin<TLeft, TRight, TKey>(this IObservable<TableUpdate<TLeft, TKey>> left_source, IObservable<TableUpdate<TRight, TKey>> right_source,
|
||||
Func<TLeft, TKey> left_key, Func<TRight, TKey> right_key, TRight right_default = default)
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace bts.md
|
||||
{
|
||||
if (!timeout.HasValue)
|
||||
{
|
||||
timeout = TimeSpan.FromHours(24);
|
||||
timeout = TimeSpan.FromMilliseconds(10000);
|
||||
}
|
||||
CancellationTokenSource src = new CancellationTokenSource(timeout.Value);
|
||||
SecurityImmutable? security = await Task.Run<SecurityImmutable?>(async () =>
|
||||
@@ -234,8 +234,6 @@ namespace bts.md
|
||||
sec = find_security(filter);
|
||||
if (sec == null)
|
||||
await Task.Delay(500);
|
||||
if(src.IsCancellationRequested)
|
||||
break;
|
||||
}
|
||||
while (sec == null);
|
||||
return sec;
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
|
||||
namespace bts.utils {
|
||||
public static class EqualityComparer {
|
||||
const int _hash_seed = 63607, _hash_multiplier = 6451;
|
||||
const int _hash_seed = 63607, _hash_multiplier = 6451, _max_auto_phase_buckets = 16;
|
||||
|
||||
public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals) =>
|
||||
new AnonymousEqualityComparer<T>(equals);
|
||||
@@ -75,6 +75,35 @@ namespace bts.utils {
|
||||
public static int aggregate_hash<T>(params T[] items) =>
|
||||
aggregate_hash(items.AsEnumerable());
|
||||
|
||||
public static int auto_phase(params int[] hash_codes) =>
|
||||
auto_phase(Array.AsReadOnly(hash_codes));
|
||||
|
||||
public static int auto_phase<T>(IEnumerable<T> items) =>
|
||||
auto_phase(items.Take(_max_auto_phase_buckets).ToList());
|
||||
|
||||
public static int auto_phase<T>(IReadOnlyList<T> hash_codes) {
|
||||
// don't divide by zero
|
||||
if (hash_codes.Count == 0)
|
||||
return 0;
|
||||
int size = Math.Min(hash_codes.Count, _max_auto_phase_buckets);
|
||||
int bits_per_hash = 32 / size;
|
||||
int phase = 1 << (bits_per_hash - 1);
|
||||
int rv = hash_codes[0].GetHashCode() % phase;
|
||||
/// The idea is to split the 32 to bits into evenly sized buckets for each hash code item.
|
||||
/// For example:
|
||||
/// If there are 4 items in <paramref name="hash_codes"/>,
|
||||
/// each item hash will occupy 8 bits in the cumulative hash (i.e., rv).
|
||||
/// The cumulative hash is shifted by 8 bits with each iteration,
|
||||
/// so the next item hash can be inserted in the front 8 bits.
|
||||
for (int i = 1; i < size; i++) {
|
||||
// nudge the bits left to make room for the next hash code
|
||||
rv = rv << bits_per_hash;
|
||||
// put this hash code in the front bits that were just zeroed out.
|
||||
rv |= hash_codes[i].GetHashCode() % phase;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static int phase_hash(int raw_item_hash, int phase_mult, int mod = MathUtils.Primes.P_1087) =>
|
||||
unchecked(raw_item_hash % mod + (mod * phase_mult));
|
||||
|
||||
|
||||
@@ -117,5 +117,18 @@ namespace bts {
|
||||
}
|
||||
internal static event Action<string, object> OnSystemCrashing;
|
||||
|
||||
public static void debug_assert(this Logger logger, bool test, string message = "", [CallerFilePath] string file = "debug_assert", [CallerLineNumber] int line = 0) {
|
||||
if (!test)
|
||||
logger.debug_fatal(message, file, line);
|
||||
}
|
||||
|
||||
/// Calls <see cref="Logger.fatal(string, string, int)"/> if DEBUG, otherwise <see cref="Logger.warn(string, string, int)"/>.
|
||||
public static void debug_fatal(this Logger logger, string message, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) {
|
||||
#if DEBUG
|
||||
logger.fatal(message, file, line);
|
||||
#else
|
||||
logger.warn(message, file, line);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,7 @@ using System.Collections.Specialized;
|
||||
namespace bts.collections {
|
||||
interface IReadOnlyObservableCollection<out T> : IReadOnlyList<T>, IReadOnlyCollection<T>, INotifyCollectionChanged { }
|
||||
interface IObservableCollection<T> : IList<T>, INotifyCollectionChanged { }
|
||||
interface IObservableCollectionAddRemoveOnly<T> : ICollection<T>, IReadOnlyObservableCollection<T> {
|
||||
new int Count { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace bts.collections {
|
||||
/// <summary>
|
||||
/// Supports duplicate items (sort doesn't have to be deterministic).
|
||||
/// </summary>
|
||||
public class ObservableSortedCollection<T> : IObservableCollectionAddRemoveOnly<T>, INotifyPropertyChanged {
|
||||
IComparer<T> _comparer;
|
||||
IEqualityComparer<T> _eq_comparer;
|
||||
T[] _values;
|
||||
int _size;
|
||||
|
||||
public ObservableSortedCollection()
|
||||
: this(Comparer<T>.Default) { }
|
||||
|
||||
public ObservableSortedCollection(IComparer<T> comparer)
|
||||
: this(comparer, EqualityComparer<T>.Default) { }
|
||||
|
||||
public ObservableSortedCollection(IComparer<T> comparer, IEqualityComparer<T> eq_comparer, int capacity = 4) {
|
||||
_comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
|
||||
_eq_comparer = eq_comparer ?? throw new ArgumentNullException(nameof(eq_comparer));
|
||||
_values = new T[capacity > 0 ? capacity : throw ExceptionBuilder.ArgumentOutOfRange.lte_zero(nameof(capacity))];
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public T this[int index] => _values[index];
|
||||
public int Count => _size;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public IComparer<T> comparer {
|
||||
get => _comparer;
|
||||
set {
|
||||
_comparer = value ?? throw new ArgumentNullException(nameof(value));
|
||||
_resort();
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(comparer)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T item) {
|
||||
int i = _binary_search(item);
|
||||
_insert_and_notify(i < 0 ? ~i : i, item);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
if (_size > 0) {
|
||||
Array.Clear(_values, 0, _size);
|
||||
_size = 0;
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CollectionHelper.indexer_name));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) => _binary_search(item) >= 0;
|
||||
public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_values, 0, array, arrayIndex, _size);
|
||||
public bool Remove(T item) {
|
||||
int pos = _binary_search(item);
|
||||
if (pos < 0)
|
||||
return false;
|
||||
_remove_and_notify(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() {
|
||||
for (int i = 0; i < _size; i++)
|
||||
yield return _values[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
void _resort() {
|
||||
var new_values = new T[_values.Length];
|
||||
Array.Copy(_values, 0, new_values, 0, _size);
|
||||
Array.Sort(new_values, 0, _size, _comparer);
|
||||
new ListWrapper(this).sync_with(new_values.WrapSubRange(0, _size), _eq_comparer);
|
||||
}
|
||||
|
||||
int _binary_search(T item) =>
|
||||
CollectionHelper.binary_search(_values, item, 0, _size, _comparer, _eq_comparer);
|
||||
|
||||
void _insert_and_notify(int index, T item) {
|
||||
ArrayHelper.insert(ref _values, index, item, ref _size);
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CollectionHelper.indexer_name));
|
||||
}
|
||||
|
||||
void _remove_and_notify(int index) {
|
||||
var cc = CollectionChanged;
|
||||
if (cc == null) {
|
||||
ArrayHelper.remove_at(ref _values, ref _size, index);
|
||||
} else {
|
||||
var old_item = _values[index];
|
||||
ArrayHelper.remove_at(ref _values, ref _size, index);
|
||||
cc(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old_item, index));
|
||||
}
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CollectionHelper.indexer_name));
|
||||
}
|
||||
|
||||
/// This <see cref="IList{T}"/> impl should not be exposed to the public,
|
||||
/// as it has the potential to break the sort order via incorrect Insert calls.
|
||||
/// The only reason it is used internally is to call <see cref="GlobalExtensions.sync_with{T}(IList{T}, IEnumerable{T})"/>.
|
||||
class ListWrapper : IList<T> {
|
||||
readonly ObservableSortedCollection<T> _target;
|
||||
|
||||
public ListWrapper(ObservableSortedCollection<T> target) =>
|
||||
_target = target;
|
||||
|
||||
public T this[int index] {
|
||||
get => _target[index];
|
||||
set {
|
||||
_target._remove_and_notify(index);
|
||||
_target._insert_and_notify(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _target.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Insert(int index, T item) => _target._insert_and_notify(index, item);
|
||||
public void RemoveAt(int index) => _target._remove_and_notify(index);
|
||||
public IEnumerator<T> GetEnumerator() => _target.GetEnumerator();
|
||||
|
||||
int IList<T>.IndexOf(T item) => _target._binary_search(item);
|
||||
void ICollection<T>.Add(T item) => _target.Add(item);
|
||||
void ICollection<T>.Clear() => _target.Clear();
|
||||
bool ICollection<T>.Contains(T item) => _target.Contains(item);
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex) => _target.CopyTo(array, arrayIndex);
|
||||
bool ICollection<T>.Remove(T item) => _target.Remove(item);
|
||||
IEnumerator IEnumerable.GetEnumerator() => _target.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.utils {
|
||||
@@ -44,16 +45,45 @@ namespace bts.utils {
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static T[] remove<T>(T[] source, T value) {
|
||||
int i = Array.IndexOf(source, value);
|
||||
return i < 0 ? source : remove_at(source, i);
|
||||
public static void insert<T>(ref T[] array, int index, T value, ref int size) {
|
||||
// validate size/index
|
||||
if (size < 0)
|
||||
throw ExceptionBuilder.ArgumentOutOfRange.lt_zero(nameof(size));
|
||||
if (size > array.Length)
|
||||
throw ExceptionBuilder.ArgumentOutOfRange.gt_length(nameof(size));
|
||||
if (index < 0)
|
||||
throw ExceptionBuilder.IndexOutOfRange.lt_zero();
|
||||
if (index > size)
|
||||
throw ExceptionBuilder.IndexOutOfRange.gt_length();
|
||||
T[] old_array = array;
|
||||
checked {
|
||||
if (size == array.Length) {
|
||||
array = new T[array.Length * 2];
|
||||
Array.Copy(old_array, 0, array, 0, index);
|
||||
}
|
||||
if (index < size)
|
||||
Array.Copy(old_array, index, array, index + 1, size - index);
|
||||
array[index] = value;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
public static T[] remove_at<T>(T[] array, int index) {
|
||||
T[] clone = new T[array.Length - 1];
|
||||
Array.Copy(array, 0, clone, 0, index);
|
||||
Array.Copy(array, index + 1, clone, index, clone.Length - index);
|
||||
return clone;
|
||||
public static void remove_at<T>(ref T[] array, ref int size, int index) {
|
||||
// validate size/index
|
||||
if (size < 0)
|
||||
throw ExceptionBuilder.ArgumentOutOfRange.lt_zero(nameof(size));
|
||||
if (size > array.Length)
|
||||
throw ExceptionBuilder.ArgumentOutOfRange.gte_length(nameof(size));
|
||||
if (index < 0)
|
||||
throw ExceptionBuilder.IndexOutOfRange.lt_zero();
|
||||
if (index >= size)
|
||||
throw ExceptionBuilder.IndexOutOfRange.gte_length();
|
||||
// shift [any]items right of 'index' left 1 pos.
|
||||
if (index < size - 1) {
|
||||
Array.Copy(array, index + 1, array, index, size - index - 1);
|
||||
}
|
||||
// clear last item, and decrement size
|
||||
Array.Clear(array, --size, 1);
|
||||
}
|
||||
|
||||
public static void remove_sorted<T>(ref T[] array, ref int size, T value, IComparer<T> comparer) =>
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Linq;
|
||||
|
||||
namespace bts.utils {
|
||||
static class CollectionHelper {
|
||||
public const string indexer_name = "Item[]";
|
||||
|
||||
public static int binary_search<T>(IList<T> items, T value, IComparer<T> comparer) =>
|
||||
binary_search(items, value, 0, items.Count, comparer);
|
||||
|
||||
@@ -27,6 +29,54 @@ namespace bts.utils {
|
||||
return ~lo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="binary_search{T}(IList{T}, T, IComparer{T})"/>, but doesn't require deterministic sort
|
||||
/// (i.e., a comparison of adjacent items in the collection may yield '0').
|
||||
/// </summary>
|
||||
public static int binary_search<T>(IList<T> items, T value, IComparer<T> comparer, IEqualityComparer<T> eq_comparer) =>
|
||||
binary_search(items, value, 0, items.Count, comparer, eq_comparer);
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="binary_search{T}(IList{T}, T, int, int, IComparer{T})"/>, but doesn't require deterministic sort
|
||||
/// (i.e., a comparison of adjacent items in the collection may yield '0').
|
||||
/// </summary>
|
||||
public static int binary_search<T>(IList<T> items, T value, int index, int length, IComparer<T> comparer, IEqualityComparer<T> eq_comparer) {
|
||||
if (comparer == null) comparer = Comparer<T>.Default;
|
||||
int lo = index;
|
||||
int hi = index + length - 1;
|
||||
while (lo <= hi) {
|
||||
int i = lo + ((hi - lo) >> 1);
|
||||
int c = comparer.Compare(items[i], value);
|
||||
if (c == 0) {
|
||||
if (eq_comparer.Equals(items[i], value)) {
|
||||
return i;
|
||||
} else {
|
||||
lo = i;
|
||||
goto try_find_exact;
|
||||
}
|
||||
}
|
||||
if (c < 0) {
|
||||
lo = i + 1;
|
||||
} else {
|
||||
hi = i - 1;
|
||||
}
|
||||
}
|
||||
return ~lo;
|
||||
try_find_exact:
|
||||
// move right
|
||||
for (hi = lo + 1; hi < items.Count && comparer.Compare(items[hi], value) == 0; hi++) {
|
||||
if (eq_comparer.Equals(items[hi], value))
|
||||
return hi;
|
||||
}
|
||||
hi = lo;
|
||||
// move left
|
||||
while (--lo >= 0 && comparer.Compare(items[lo], value) == 0) {
|
||||
if (eq_comparer.Equals(items[lo], value))
|
||||
return lo;
|
||||
}
|
||||
return ~hi;
|
||||
}
|
||||
|
||||
public static void reflect_change<T, TData>(IList<T> target, IEnumerable<TData> source, Func<TData, T> create_item, Action<T> on_removed, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
|
||||
File renamed without changes.
@@ -14,22 +14,34 @@ namespace mbts.utils {
|
||||
|
||||
public static ArgumentException not_of_type(object value, string name, Type t) =>
|
||||
new ArgumentException($"Value is not of the expected type '{t}.'");
|
||||
|
||||
public static ArgumentException null_or_empty(string name) =>
|
||||
new ArgumentException("Value is null or empty.", name);
|
||||
}
|
||||
|
||||
public static class IndexOutOfRange {
|
||||
public static IndexOutOfRangeException lt_zero() =>
|
||||
new IndexOutOfRangeException("Index is less than 0");
|
||||
|
||||
public static IndexOutOfRangeException gt_length() =>
|
||||
new IndexOutOfRangeException("Index is greater than the number of elements.");
|
||||
|
||||
public static IndexOutOfRangeException gte_length() =>
|
||||
new IndexOutOfRangeException("Index is greater than or equal to the number of elements.");
|
||||
}
|
||||
|
||||
public static class ArgumentOutOfRange {
|
||||
public static ArgumentOutOfRangeException lt_zero(string name) =>
|
||||
new ArgumentOutOfRangeException(name, "Index is less than 0");
|
||||
new ArgumentOutOfRangeException(name, "Value is less than 0.");
|
||||
|
||||
public static ArgumentOutOfRangeException lte_zero(string name) =>
|
||||
new ArgumentOutOfRangeException(name, "Value is less than or equal to 0.");
|
||||
|
||||
public static ArgumentOutOfRangeException gt_length(string name) =>
|
||||
new ArgumentOutOfRangeException(name, "Value is greater than the number of elements.");
|
||||
|
||||
public static ArgumentOutOfRangeException gte_length(string name) =>
|
||||
new ArgumentOutOfRangeException(name, "Index is greater than or equal to the number of elements.");
|
||||
new ArgumentOutOfRangeException(name, "Value is greater than or equal to the number of elements.");
|
||||
}
|
||||
|
||||
public static class Format {
|
||||
|
||||
@@ -19,28 +19,29 @@ namespace bts.utils {
|
||||
public static TableUpdate<T, TKey> with_changes<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<Change<T>> changes) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changes, Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> RootDependent<T, TKey>(
|
||||
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TResult, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, T> with_root)
|
||||
Func<T, T, TResult> with_root,
|
||||
Func<T, TResult> without_root,
|
||||
Func<TResult, TKey> result_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
RootDependent(source, key, parent_key, with_root, v => v, EqualityComparer<T>.Default, default).SelectKeyValue(kv => kv.Value, key);
|
||||
RootDependent(source, key, parent_key, with_root, without_root, result_key, default);
|
||||
|
||||
// TODO: unit tests
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> RootDependent<T, TKey, TResult>(
|
||||
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
IEqualityComparer<TResult> result_comparer,
|
||||
Func<TResult, TKey> result_key,
|
||||
TKey no_parent_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
|
||||
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
|
||||
Observable.Create<TableUpdate<TResult, TKey>>(observer => {
|
||||
var state = DictionaryHelper.empty<TKey, T>();
|
||||
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
|
||||
var output = TableUpdate<TResult, TKey>.mk_empty(result_key);
|
||||
var parent_to_children = new SetMap<TKey, T>();
|
||||
return source.Subscribe(update);
|
||||
//
|
||||
@@ -63,9 +64,9 @@ namespace bts.utils {
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
var ck = key(child);
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(ck, get_result_no_parent(child)));
|
||||
builder.add_or_update(ck, get_result_no_parent(child));
|
||||
foreach (var d in descendents(ck))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, child)));
|
||||
builder.add_or_update(key(d), get_result(d, child));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,11 +77,11 @@ namespace bts.utils {
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, result));
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
foreach (var v in input.added) {
|
||||
var k = input.key_extractor(v);
|
||||
@@ -89,23 +90,21 @@ namespace bts.utils {
|
||||
if (pk.Equals(no_parent_key)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
parent_to_children.add(pk, v);
|
||||
var root = get_root(v);
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
}
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, result));
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
var new_output = builder.to_table_checked(value_comparer);
|
||||
if (new_output.has_changes())
|
||||
observer.OnNext(output = new_output);
|
||||
observer.OnNext(output = builder.to_table());
|
||||
}
|
||||
T get_root(T value) {
|
||||
var pk = parent_key(value);
|
||||
|
||||
Binary file not shown.
@@ -1,300 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using bts.excel.extensions;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a reference to some Cell instance, whether it exists or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Static methods are provided for conversion of column names etc.
|
||||
/// </remarks>
|
||||
public sealed class CellRef : IComparable, IComparable<CellRef>
|
||||
{
|
||||
public CellRef(ExcelDna.Integration.ExcelReference rref) : this(rref.GetSheetName(),
|
||||
(rref.ColumnFirst + 1, rref.RowFirst + 1) )
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the class: CellRef
|
||||
/// </summary>
|
||||
/// <param name="sheetName">Name of the sheet where the cell belongs.</param>
|
||||
/// <param name="coordinate">Coordinate (column, row) for the cell in the sheet.</param>
|
||||
public CellRef(string sheetName, (int col1, int row1) coordinate)
|
||||
{
|
||||
SheetName = sheetName;
|
||||
SimpleSheetName = simple_sheet_name(SheetName);
|
||||
Coordinate = coordinate;
|
||||
LocalAddress = ConvertCoordToAddress(coordinate);
|
||||
}
|
||||
|
||||
static string simple_sheet_name(string sn)
|
||||
{
|
||||
int open = sn.IndexOf('[');
|
||||
int close = sn.IndexOf(']', open + 1);
|
||||
var ssn = sn.Substring(0, open) + sn.Substring(close + 1);
|
||||
ssn = stringize_workbook(ssn);
|
||||
return ssn;
|
||||
}
|
||||
|
||||
public static string stringize_workbook(string ssn)
|
||||
{
|
||||
ssn = ssn.Replace("[", "").Replace("]", "_").Replace(".", "_");
|
||||
return ssn;
|
||||
}
|
||||
|
||||
public string named_range { get; set;}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of rows, to e compatible with Excel.
|
||||
/// </summary>
|
||||
private const int MaxRows = 1048576;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of columns, to be compatible with Excel.
|
||||
/// </summary>
|
||||
private const int MaxColumns = 16384;
|
||||
|
||||
// NOTE: Worksheet and workbook specifications and limits
|
||||
// http://office.microsoft.com/en-gb/excel-help/excel-specifications-and-limits-HP010342495.aspx
|
||||
//
|
||||
// Worksheet size: 1,048,576 rows by 16,384 columns.
|
||||
// * 1048575 is the highest number representable in 20 bits.
|
||||
// * 16383 is the highest number representable in 14 bits. That leaves 2 spare bits in a 16-bit word.
|
||||
// Column width: 255 characters (I'm not sure what the real significance of this is).
|
||||
// Total number of characters that a cell can contain: 32,767 characters
|
||||
// * 32766 is the highest number representable in 15 bits. That leaves 1 spare bit in a 16-bit word.
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Name of the sheet where the cell belongs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All cells belong to some Sheet instance, so this name must never be null or empty.
|
||||
/// </remarks>
|
||||
public string SheetName { get; }
|
||||
public string SimpleSheetName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Coordinate (column, row) for the cell in the sheet.
|
||||
/// </summary>
|
||||
public (int col1, int row1) Coordinate { get; }
|
||||
|
||||
public CellRef Offset(int columnOffset, int rowOffset)
|
||||
{
|
||||
return new CellRef(SheetName, (Column + columnOffset, Row + rowOffset));
|
||||
}
|
||||
|
||||
public int Column => Coordinate.col1;
|
||||
|
||||
public int Row => Coordinate.row1;
|
||||
|
||||
public string LocalAddress { get; internal set; }
|
||||
|
||||
#region Address conversion
|
||||
|
||||
/// <summary>
|
||||
/// Converts a column identifier, using letters, to a column number.
|
||||
/// </summary>
|
||||
/// <param name="columnName">Column identifier using letters.</param>
|
||||
/// <returns>Column number.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">When column number exceeds maximum.</exception>
|
||||
public static int ConvertToColumnNumber(string columnName)
|
||||
{
|
||||
var number = 0;
|
||||
var pow = 1;
|
||||
for (var i = columnName.Length - 1; i >= 0; i--)
|
||||
{
|
||||
number += (columnName[i] - 'A' + 1) * pow;
|
||||
pow *= 26;
|
||||
}
|
||||
|
||||
if (number > MaxColumns)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(columnName),
|
||||
"Column number from columnName exceeds maximum");
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a column number to a column identifier using letters.
|
||||
/// </summary>
|
||||
/// <param name="columnNumber">Column number.</param>
|
||||
/// <returns>Column identifier using letters.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">When column number is invalid.</exception>
|
||||
public static string ConvertToColumnName(int columnNumber)
|
||||
{
|
||||
if (columnNumber < 1 || columnNumber > MaxColumns)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(columnNumber),
|
||||
"Invalid column number");
|
||||
}
|
||||
|
||||
var dividend = columnNumber;
|
||||
var columnName = string.Empty;
|
||||
while (dividend > 0)
|
||||
{
|
||||
var modulo = (dividend - 1) % 26;
|
||||
columnName = Convert.ToChar(65 + modulo) + columnName;
|
||||
dividend = (dividend - modulo) / 26;
|
||||
}
|
||||
|
||||
return columnName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an address, like "A1", to a cell coordinate.
|
||||
/// </summary>
|
||||
/// <param name="address">Cell address, like "A1".</param>
|
||||
/// <returns>Coordinate of cell on sheet.</returns>
|
||||
/// <exception cref="ArgumentNullException">When address argument is null.</exception>
|
||||
/// <exception cref="ArgumentException">When address is invalid.</exception>
|
||||
public static (int col1, int row1) ConvertAddressToCoord(string address)
|
||||
{
|
||||
if (address == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
}
|
||||
|
||||
address = address.Replace("$", string.Empty);
|
||||
address = address.ToUpper();
|
||||
|
||||
var letters = new StringBuilder();
|
||||
foreach (var c in address)
|
||||
{
|
||||
if (c < 'A' || c > 'Z')
|
||||
{
|
||||
break;
|
||||
}
|
||||
letters.Append(c);
|
||||
}
|
||||
|
||||
if (letters.Length == 0 || letters.Length > 3)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Column name requires 1 to 3 letters",
|
||||
nameof(address));
|
||||
}
|
||||
|
||||
var numbers = address.Substring(letters.Length);
|
||||
if (numbers.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Row number is required", nameof(address));
|
||||
}
|
||||
|
||||
Debug.Assert(letters.Length + numbers.Length == address.Length);
|
||||
|
||||
var row1 = int.Parse(numbers);
|
||||
var col1 = ConvertToColumnNumber(letters.ToString());
|
||||
|
||||
return (col1, row1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert cell coordinate to cell address, like "A1".
|
||||
/// </summary>
|
||||
/// <param name="coordinate">Coordinate of cell on sheet.</param>
|
||||
/// <param name="absolute">Provide absolute address, like "$A$1".</param>
|
||||
/// <returns>Cell address, like "A1", or optionally like "$A$1".</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static string ConvertCoordToAddress((int col1, int row1) coordinate, bool absolute = false)
|
||||
{
|
||||
if (coordinate.row1 < 1 || coordinate.row1 > MaxRows ||
|
||||
coordinate.col1 < 1 || coordinate.col1 > MaxColumns)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(coordinate),
|
||||
"Invalid coordinate");
|
||||
}
|
||||
|
||||
var (col1, row1) = coordinate;
|
||||
return absolute
|
||||
? $"${ConvertToColumnName(col1)}${row1}"
|
||||
: $"{ConvertToColumnName(col1)}{row1}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IComparable
|
||||
|
||||
public int CompareTo(CellRef other)
|
||||
{
|
||||
if (other == null) return -1;
|
||||
return string.Compare(ToString(), other.ToString(),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj.GetType() != GetType()) return -1;
|
||||
return CompareTo((CellRef)obj);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HashCode & Equals
|
||||
|
||||
/// <returns>Unique key</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return to_name();
|
||||
}
|
||||
|
||||
public string to_address()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SheetName))
|
||||
return $"'{SheetName}'!{LocalAddress}";
|
||||
else
|
||||
return $"{LocalAddress}";
|
||||
}
|
||||
|
||||
public string to_name()
|
||||
{
|
||||
if(!string.IsNullOrEmpty(named_range))
|
||||
return named_range;
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SheetName))
|
||||
{
|
||||
return $"{SimpleSheetName}_{LocalAddress}".ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
return $"{LocalAddress}".ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
public string r1_c1_include_sheet()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SheetName))
|
||||
return $"'{SimpleSheetName}'!R{this.Row}C{this.Column}";
|
||||
else
|
||||
return $"R{this.Row}C{this.Column}";
|
||||
}
|
||||
|
||||
public string r1_c1()
|
||||
{
|
||||
return $"R{this.Row}C{this.Column}";
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ToString().GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null) return false;
|
||||
|
||||
if (obj != null && !(obj is CellRef))
|
||||
throw new ArgumentException("Object must be of type CellRef.");
|
||||
|
||||
return GetHashCode().Equals(obj.GetHashCode());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using spark_excel.grammar;
|
||||
using Antlr4.Runtime;
|
||||
|
||||
using ExcelDna.Integration;
|
||||
using bts.excel.extensions;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public interface IParameters
|
||||
{
|
||||
IReadOnlyDictionary<string, string> algo_params { get; }
|
||||
void register_algo_parameter(string type, string name);
|
||||
void unregister_algo_parameter(string name);
|
||||
|
||||
IReadOnlyDictionary<string, string> special_params { get; }
|
||||
void register_special_parameter(string alias, string name);
|
||||
void unregister_special_parameter(string name);
|
||||
}
|
||||
|
||||
public interface IDefinitionVisitor
|
||||
{
|
||||
void add_def(string def);
|
||||
void map_def(string def, string mapped);
|
||||
List<string> get_defs();
|
||||
|
||||
|
||||
IReadOnlyDictionary<string, string> order_setups {get;}
|
||||
void register_order_setup(string setup, string attach);
|
||||
|
||||
void add_dual_definition(string code);
|
||||
List<string> get_dual_definitions();
|
||||
|
||||
void add_dual_impl(string code);
|
||||
List<string> get_dual_impls();
|
||||
|
||||
void register_algo_parameter(string type, string name);
|
||||
}
|
||||
|
||||
public class CodeParts : IDefinitionVisitor, IParameters
|
||||
{
|
||||
List<string> _defs = new List<string>();
|
||||
ConcurrentDictionary<string, string> _def_map = new ConcurrentDictionary<string, string>();
|
||||
ConcurrentDictionary<string, string> _order_setups = new ConcurrentDictionary<string, string>();
|
||||
List<string> _dual_defs = new List<string>();
|
||||
List<string> _dual_blocks = new List<string>();
|
||||
|
||||
ConcurrentDictionary<string, string> _algo_params = new ConcurrentDictionary<string, string>();
|
||||
ConcurrentDictionary<string, string> _special_params = new ConcurrentDictionary<string, string>();
|
||||
|
||||
public CodeParts()
|
||||
{
|
||||
}
|
||||
|
||||
public string function_name { get; set; }
|
||||
public string function_impl { get; set; }
|
||||
|
||||
public void add_def(string def)
|
||||
{
|
||||
_defs.Add(def);
|
||||
}
|
||||
|
||||
public void map_def(string def, string mapped)
|
||||
{
|
||||
_def_map[def] = mapped;
|
||||
}
|
||||
|
||||
public List<string> get_defs()
|
||||
{
|
||||
var list = _defs.Distinct();
|
||||
List<string> defs = new List<string>();
|
||||
foreach (var d in list)
|
||||
defs.Add(_def_map.TryGetValue(d, out string mapped) ? mapped : d);
|
||||
return defs;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, string> order_setups => _order_setups;
|
||||
public void register_order_setup(string setup, string attach)
|
||||
{
|
||||
_order_setups[attach] = setup;
|
||||
}
|
||||
|
||||
public void add_dual_impl(string code)
|
||||
{
|
||||
_dual_blocks.Add(code);
|
||||
}
|
||||
|
||||
public List<string> get_dual_impls()
|
||||
{
|
||||
return _dual_blocks;
|
||||
}
|
||||
|
||||
public void add_dual_definition(string code)
|
||||
{
|
||||
_dual_defs.Add(code);
|
||||
}
|
||||
|
||||
public List<string> get_dual_definitions()
|
||||
{
|
||||
return _dual_defs;
|
||||
}
|
||||
|
||||
|
||||
public IReadOnlyDictionary<string, string> algo_params => _algo_params;
|
||||
public void register_algo_parameter(string type, string name)
|
||||
{
|
||||
_algo_params[name] = string.IsNullOrEmpty(type) ? "double" : type;
|
||||
}
|
||||
public void unregister_algo_parameter(string name)
|
||||
{
|
||||
_algo_params.TryRemove(name, out _);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, string> special_params => _special_params;
|
||||
public void register_special_parameter(string alias, string name)
|
||||
{
|
||||
_special_params[alias] = name;
|
||||
}
|
||||
public void unregister_special_parameter(string name)
|
||||
{
|
||||
_special_params.TryRemove(name, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public class ErrorValue : IComparable, IComparable<ErrorValue>
|
||||
{
|
||||
private enum ErrorType
|
||||
{
|
||||
Null,
|
||||
Div0,
|
||||
Value,
|
||||
Ref,
|
||||
Name,
|
||||
Num,
|
||||
NA
|
||||
}
|
||||
|
||||
public static ErrorValue Null => new ErrorValue(ErrorType.Null);
|
||||
public static ErrorValue Div0 => new ErrorValue(ErrorType.Div0);
|
||||
public static ErrorValue Value => new ErrorValue(ErrorType.Value);
|
||||
public static ErrorValue Ref => new ErrorValue(ErrorType.Ref);
|
||||
public static ErrorValue Name => new ErrorValue(ErrorType.Name);
|
||||
public static ErrorValue Num => new ErrorValue(ErrorType.Num);
|
||||
public static ErrorValue NA => new ErrorValue(ErrorType.NA);
|
||||
|
||||
private const string HashNull = "#NULL!";
|
||||
private const string HashDiv0 = "#DIV/0!";
|
||||
private const string HashValue = "#VALUE!";
|
||||
private const string HashRef = "#REF!";
|
||||
private const string HashName = "#NAME?";
|
||||
private const string HashNum = "#NUM!";
|
||||
private const string HashNA = "#N/A";
|
||||
|
||||
private readonly ErrorType _errorType;
|
||||
|
||||
public ErrorValue(string token)
|
||||
: this(GetErrorType(token))
|
||||
{ }
|
||||
|
||||
private ErrorValue(ErrorType errorType)
|
||||
{
|
||||
_errorType = errorType;
|
||||
}
|
||||
|
||||
private static ErrorType GetErrorType(string token)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case HashNull: return ErrorType.Null;
|
||||
case HashDiv0: return ErrorType.Div0;
|
||||
case HashValue: return ErrorType.Value;
|
||||
case HashRef: return ErrorType.Ref;
|
||||
case HashName: return ErrorType.Name;
|
||||
case HashNum: return ErrorType.Num;
|
||||
case HashNA: return ErrorType.NA;
|
||||
default: throw new Exception("Unrecongised error token");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsErrorToken(string token)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case HashNull:
|
||||
case HashDiv0:
|
||||
case HashValue:
|
||||
case HashRef:
|
||||
case HashName:
|
||||
case HashNum:
|
||||
case HashNA:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (_errorType)
|
||||
{
|
||||
case ErrorType.Null: return HashNull;
|
||||
case ErrorType.Div0: return HashDiv0;
|
||||
case ErrorType.Value: return HashValue;
|
||||
case ErrorType.Ref: return HashRef;
|
||||
case ErrorType.Name: return HashName;
|
||||
case ErrorType.Num: return HashNum;
|
||||
case ErrorType.NA: return HashNA;
|
||||
default: throw new Exception("Unexpected condition");
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(ErrorValue other)
|
||||
{
|
||||
if (other == null) return -1;
|
||||
return _errorType.CompareTo(other._errorType);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj.GetType() != GetType()) return -1;
|
||||
return CompareTo(obj as ErrorValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ExcelDna.Integration;
|
||||
using Microsoft.Office.Interop.Excel;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public static class RangeHelper
|
||||
{
|
||||
public static ExcelReference RangeToExcelReference(Range range)
|
||||
|
||||
{
|
||||
|
||||
return new ExcelReference(range.Row - 1,
|
||||
range.Row - 1 + range.Rows.Count - 1,
|
||||
range.Column - 1,
|
||||
range.Column - 1 + range.Columns.Count - 1,
|
||||
range.Worksheet.Name);
|
||||
|
||||
}
|
||||
public static Range ReferenceToRange(ExcelReference xlref)
|
||||
{
|
||||
var app = ExcelDnaUtil.Application as Microsoft.Office.Interop.Excel.Application;
|
||||
var refText = (string)XlCall.Excel(XlCall.xlfReftext, xlref, true);
|
||||
var range = app.Range[refText];
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
// Select an ExcelReference (perhaps on another sheet) allowing changes to be made there.
|
||||
// On clean-up, resets all the selections and the active sheet.
|
||||
// Should not be used if the work you are going to do will switch sheets, amke new sheets etc.
|
||||
public class ExcelSelectionHelper : XlCall, IDisposable
|
||||
{
|
||||
object oldSelectionOnActiveSheet;
|
||||
object oldActiveCellOnActiveSheet;
|
||||
|
||||
object oldSelectionOnRefSheet;
|
||||
object oldActiveCellOnRefSheet;
|
||||
|
||||
public ExcelSelectionHelper(ExcelReference refToSelect)
|
||||
{
|
||||
// Remember old selection state on the active sheet
|
||||
oldSelectionOnActiveSheet = Excel(xlfSelection);
|
||||
oldActiveCellOnActiveSheet = Excel(xlfActiveCell);
|
||||
|
||||
// Switch to the sheet we want to select
|
||||
string refSheet = (string)Excel(xlSheetNm, refToSelect);
|
||||
Excel(xlcWorkbookSelect, new object[] { refSheet });
|
||||
|
||||
// record selection and active cell on the sheet we want to select
|
||||
oldSelectionOnRefSheet = Excel(xlfSelection);
|
||||
oldActiveCellOnRefSheet = Excel(xlfActiveCell);
|
||||
|
||||
// make the selection
|
||||
Excel(xlcFormulaGoto, refToSelect);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Reset the selection on the target sheet
|
||||
Excel(xlcSelect, oldSelectionOnRefSheet, oldActiveCellOnRefSheet);
|
||||
|
||||
// Reset the sheet originally selected
|
||||
string oldActiveSheet = (string)Excel(xlSheetNm, oldSelectionOnActiveSheet);
|
||||
Excel(xlcWorkbookSelect, new object[] { oldActiveSheet });
|
||||
|
||||
// Reset the selection in the active sheet (some bugs make this change sometimes too)
|
||||
Excel(xlcSelect, oldSelectionOnActiveSheet, oldActiveCellOnActiveSheet);
|
||||
}
|
||||
}
|
||||
|
||||
public class ExcelEchoOffHelper : XlCall, IDisposable
|
||||
{
|
||||
object oldEcho;
|
||||
|
||||
public ExcelEchoOffHelper()
|
||||
{
|
||||
oldEcho = Excel(xlfGetWorkspace, 40);
|
||||
Excel(xlcEcho, false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Excel(xlcEcho, oldEcho);
|
||||
}
|
||||
}
|
||||
|
||||
public class ExcelCalculationManualHelper : XlCall, IDisposable
|
||||
{
|
||||
object oldCalculationMode;
|
||||
|
||||
public ExcelCalculationManualHelper()
|
||||
{
|
||||
oldCalculationMode = Excel(xlfGetDocument, 14);
|
||||
Excel(xlcOptionsCalculation, 3);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Excel(xlcOptionsCalculation, oldCalculationMode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
using spark_excel.grammar;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public interface IExpression
|
||||
{
|
||||
object evaluate();
|
||||
}
|
||||
internal class Expression<T> : IExpression
|
||||
{
|
||||
private readonly ExcelFormulaParser.ExprContext _context;
|
||||
private readonly IExcelFormulaVisitor<T> _visitor;
|
||||
|
||||
public Expression(ExcelFormulaParser.ExprContext context, IExcelFormulaVisitor<T> visitor)
|
||||
{
|
||||
_context = context;
|
||||
_visitor = visitor;
|
||||
}
|
||||
|
||||
public object evaluate()
|
||||
{
|
||||
return _context.Accept(_visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using ExcelDna.Integration;
|
||||
using spark_excel.grammar;
|
||||
|
||||
using Antlr4.Runtime;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
internal class ExcelFormulaErrorListener : BaseErrorListener
|
||||
{
|
||||
public bool IsValid { get; private set; } = true;
|
||||
public string offendingSymbol { get; private set;}
|
||||
public int line { get; private set; }
|
||||
public int charPositionInLine { get; private set; }
|
||||
public string msg { get; private set; }
|
||||
public RecognitionException exception { get; private set;}
|
||||
|
||||
public override void SyntaxError(IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
|
||||
{
|
||||
IsValid = false;
|
||||
this.offendingSymbol = offendingSymbol.Text;
|
||||
this.line = line;
|
||||
this.charPositionInLine = charPositionInLine;
|
||||
this.msg = msg;
|
||||
this.exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
public class Generator
|
||||
{
|
||||
readonly IParameters _globals = new CodeParts();
|
||||
public IParameters globals => _globals;
|
||||
|
||||
public string bts_generate_algo_class(string algoname, IEnumerable<CodeParts> functions, string[] filepaths = null)
|
||||
{
|
||||
var ass = Assembly.GetExecutingAssembly();
|
||||
var resources = ass.GetManifestResourceNames();
|
||||
var res = $"{ass.GetName().Name}.ExcelAlgoTemplate.cpp";
|
||||
|
||||
using (Stream stream = ass.GetManifestResourceStream(res))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(stream))
|
||||
{
|
||||
string cpp = reader.ReadToEnd();
|
||||
//cpp = cpp.Replace("#define __AlgoName__", "//#define __#AlgoName__");
|
||||
cpp = cpp.Replace("#define __FunctionDefs__", "//#define __#FunctionDefs__");
|
||||
cpp = cpp.Replace("#define __FunctionCalls__", "//#define __#FunctionCalls__");
|
||||
cpp = cpp.Replace("#define __FunctionImpls__", "//#define __#FunctionImpls__");
|
||||
|
||||
cpp = cpp.Replace("#define __DualDefs__", "//#define __#DualDefs__");
|
||||
cpp = cpp.Replace("#define __DualImpls__", "//#define __#DualImpls__");
|
||||
|
||||
cpp = cpp.Replace("#define __ParameterDefs__", "//#define __#ParameterDefs__");
|
||||
cpp = cpp.Replace("#define __ParameterLaunches__", "//#define __#ParameterLaunches__");
|
||||
cpp = cpp.Replace("#define __ParameterUpdates__", "//#define __#ParameterUpdates__");
|
||||
cpp = cpp.Replace("#define __ParameterSetups__", "//#define __#ParameterSetups__");
|
||||
cpp = cpp.Replace("#define __OrderSetups__", "//#define __#OrderSetups__");
|
||||
cpp = cpp.Replace("#define __OrderAttaches__", "//#define __#OrderAttaches__");
|
||||
cpp = cpp.Replace("__Remove__", string.Empty);
|
||||
cpp = cpp.Replace("__AlgoName__", algoname);
|
||||
|
||||
string funcdefs = string.Join(Environment.NewLine, functions.Select(x => $" void {x.function_name}();"));
|
||||
cpp = cpp.Replace("__FunctionDefs__", "//__FunctionDefs__" + Environment.NewLine + funcdefs);
|
||||
|
||||
string funccalls = string.Join(Environment.NewLine, functions.Select(x => " " + x.function_name + "();" ));
|
||||
cpp = cpp.Replace("__FunctionCalls__", "//__FunctionCalls__" + Environment.NewLine + funccalls);
|
||||
|
||||
string funcimpls = string.Join(Environment.NewLine, functions.Select(x => x.function_impl.Replace("__AlgoName__", algoname) ));
|
||||
cpp = cpp.Replace("__FunctionImpls__", "//__FunctionImpls__" + Environment.NewLine + funcimpls);
|
||||
|
||||
string dualdefs = string.Join(Environment.NewLine, functions.SelectMany(x => x.get_dual_definitions().Select( xx => xx.Replace("__AlgoName__::", "") ) ).Distinct());
|
||||
cpp = cpp.Replace("__DualDefs__", "//__DualDefs__" + Environment.NewLine + dualdefs);
|
||||
|
||||
string dualimpls = string.Join(Environment.NewLine, functions.SelectMany(x => x.get_dual_impls().Select(xx => xx.Replace("__AlgoName__", algoname))).Distinct());
|
||||
cpp = cpp.Replace("__DualImpls__", "//__DualImpls__" + Environment.NewLine + dualimpls);
|
||||
|
||||
//__ParameterDefs__
|
||||
string globalparamdefs = string.Join(Environment.NewLine, globals.algo_params.Select( kv => " " + kv.Value + " " + kv.Key + ";"));
|
||||
string localparamdefs = string.Join(Environment.NewLine, functions.SelectMany(x => x.algo_params).Select(kv => " " + kv.Value + " " + kv.Key + ";"));
|
||||
cpp = cpp.Replace("__ParameterDefs__",
|
||||
"//__ParameterDefs__" + Environment.NewLine + globalparamdefs + Environment.NewLine + localparamdefs);
|
||||
|
||||
//__ParameterLaunches__
|
||||
{
|
||||
List<string> launches = new List<string>();
|
||||
foreach (var kv in globals.algo_params)
|
||||
{
|
||||
string launch = $@"
|
||||
auto {kv.Key}_param = params.find(""{kv.Key}"");
|
||||
if ( {kv.Key}_param == params.end() )
|
||||
return OrderOpResult{{ ORDER_ERROR_INVALID_ORDER_DEFINITON,""{kv.Key} parameter must be defined""}};
|
||||
me->{kv.Key} = {kv.Key}_param->second.{get_as(kv.Key, kv.Value)}();
|
||||
";
|
||||
launches.Add(launch);
|
||||
}
|
||||
string paramlaunch = string.Join(Environment.NewLine, launches);
|
||||
cpp = cpp.Replace("__ParameterLaunches__", "//__ParameterLaunches__" + Environment.NewLine + paramlaunch);
|
||||
}
|
||||
|
||||
//__ParameterUpdates__
|
||||
{
|
||||
List<string> updates = new List<string>();
|
||||
foreach(var kv in globals.algo_params)
|
||||
{
|
||||
string update = $@"
|
||||
auto {kv.Key}_param = params.find(""{kv.Key}"");
|
||||
if ( {kv.Key}_param == params.end() )
|
||||
{{
|
||||
algo_services().terminate_error(""Can't find parameter {kv.Key} "");
|
||||
return;
|
||||
}}
|
||||
me->{kv.Key} = {kv.Key}_param->second.{get_as(kv.Key, kv.Value)}();
|
||||
";
|
||||
updates.Add(update);
|
||||
}
|
||||
string paramupdates = string.Join(Environment.NewLine, updates);
|
||||
cpp = cpp.Replace("__ParameterUpdates__", "//__ParameterUpdates__" + Environment.NewLine + paramupdates);
|
||||
}
|
||||
|
||||
//__ParameterSetups__
|
||||
{
|
||||
List<string> setups = new List<string>();
|
||||
setups.Add($@"
|
||||
DynamicOrderDescriptor algo_desc;
|
||||
algo_desc.set_dynamic_order_type(""{algoname}"");
|
||||
");
|
||||
foreach (var kv in globals.algo_params)
|
||||
{
|
||||
string setup = $@"
|
||||
auto {kv.Key}_param = algo_desc.add_param_desc();
|
||||
{kv.Key}_param->set_param_name(""{kv.Key}"");
|
||||
{kv.Key}_param->mutable_param_type()->set_primitive({get_param_type_enum(kv.Key, kv.Value)});
|
||||
";
|
||||
setups.Add(setup);
|
||||
}
|
||||
setups.Add($@"
|
||||
serv.register_dynamic_algo_order(algo_desc, []() {{ return std::make_unique<{algoname}>(); }});
|
||||
");
|
||||
string paramsetups = string.Join(Environment.NewLine, setups);
|
||||
cpp = cpp.Replace("__ParameterSetups__", "//__ParameterSetups__" + Environment.NewLine + paramsetups);
|
||||
}
|
||||
|
||||
//__OrderSetups__
|
||||
{
|
||||
var setups = functions.SelectMany(x => x.order_setups.Values.SelectMany(xx => xx.Split(new[]{'\r','\n'})));
|
||||
string ordersetups = string.Join(Environment.NewLine, setups.Distinct());
|
||||
cpp = cpp.Replace("__OrderSetups__", "//__OrderSetups__" + Environment.NewLine + ordersetups);
|
||||
}
|
||||
//__OrderAttaches__
|
||||
{
|
||||
var attach = functions.SelectMany(x => x.order_setups.Keys);
|
||||
string orderattach = string.Join(Environment.NewLine, attach);
|
||||
cpp = cpp.Replace("__OrderAttaches__", "//__OrderAttaches__" + Environment.NewLine + orderattach);
|
||||
}
|
||||
|
||||
string cppname = string.Empty;
|
||||
if(filepaths != null && filepaths.Length > 0)
|
||||
try_write_file_fullpath(filepaths, cpp);
|
||||
else
|
||||
try_write_file(algoname + ".cpp", cpp);
|
||||
return cppname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private string get_as(string paramname, string paramtype)
|
||||
{
|
||||
switch (paramtype.ToLower())
|
||||
{
|
||||
case "int":
|
||||
return "as_int32";
|
||||
case "uint":
|
||||
return "as_uint32";
|
||||
case "double":
|
||||
return "as_double";
|
||||
case "string":
|
||||
return "as_string";
|
||||
case "bool":
|
||||
return "as_bool";
|
||||
case "order":
|
||||
case "orderdef":
|
||||
return "as_order_def";
|
||||
default:
|
||||
throw new ArgumentException("unknown type " + paramname + " " + paramtype);
|
||||
}
|
||||
}
|
||||
|
||||
static private string get_param_type_enum(string paramname, string paramtype)
|
||||
{
|
||||
switch(paramtype.ToLower())
|
||||
{
|
||||
case "int":
|
||||
return "DYNAMIC_ORDER_PRIMITIVE_INT";
|
||||
case "uint":
|
||||
return "DYNAMIC_ORDER_PRIMITIVE_UINT";
|
||||
case "double":
|
||||
return "DYNAMIC_ORDER_PRIMITIVE_DOUBLE";
|
||||
case "string":
|
||||
return "DYNAMIC_ORDER_PRIMITIVE_STRING";
|
||||
case "bool":
|
||||
return "DYNAMIC_ORDER_PRIMITIVE_BOOL";
|
||||
case "order":
|
||||
case "orderdef":
|
||||
return "DYNAMIC_ORDER_PRIMITIVE_ORDER_DEF";
|
||||
default:
|
||||
throw new ArgumentException("unknown type " + paramname + " " + paramtype);
|
||||
}
|
||||
}
|
||||
|
||||
//[ExcelFunction(IsMacroType = true, IsThreadSafe = false)]
|
||||
//we can't call this as a UDF or the array / selectspecial thing doesn't always work
|
||||
public CodeParts bts_generate_function_code(ExcelReference rref, ref string function_name)
|
||||
{
|
||||
var formula = XlCall.Excel(XlCall.xlfGetCell, 41, rref);
|
||||
|
||||
var antlrstream = new AntlrInputStream(formula.ToString());
|
||||
var lexer = new ExcelFormulaLexer(antlrstream);
|
||||
var tokens = new CommonTokenStream(lexer);
|
||||
var parser = new ExcelFormulaParser(tokens);
|
||||
|
||||
var error_listener = new ExcelFormulaErrorListener();
|
||||
//parser.RemoveErrorListeners();
|
||||
parser.AddErrorListener(error_listener);
|
||||
var parsetree = parser.main();
|
||||
|
||||
if (!error_listener.IsValid)
|
||||
{
|
||||
try_write_file(function_name, error_listener.offendingSymbol + error_listener.msg + error_listener.exception?.ToString());
|
||||
throw new Exception(error_listener.offendingSymbol + error_listener.msg, error_listener.exception);
|
||||
}
|
||||
|
||||
CodeParts defs = new CodeParts();
|
||||
GeneratorVisitor visitor = new GeneratorVisitor(rref, defs, globals);
|
||||
object val = visitor.VisitMain(parsetree);
|
||||
|
||||
if(string.IsNullOrEmpty(function_name))
|
||||
function_name = new CellRef(rref).to_name();
|
||||
|
||||
string ret = string.Empty;
|
||||
ret += Environment.NewLine;
|
||||
ret += $"void __AlgoName__::{function_name}() {Environment.NewLine}{{{Environment.NewLine}";
|
||||
defs.get_defs().ForEach(x => ret += " " + x + Environment.NewLine);
|
||||
ret += " " + val.ToString() + " ;";
|
||||
ret += $"{Environment.NewLine} }}";
|
||||
defs.function_impl = ret;
|
||||
defs.function_name = function_name;
|
||||
try_write_file(function_name + ".function", ret);
|
||||
return defs;
|
||||
}
|
||||
|
||||
static string try_write_file(string filename, string text)
|
||||
{
|
||||
string fname = string.Empty;
|
||||
try
|
||||
{
|
||||
fname = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\{filename}";
|
||||
using(System.IO.StreamWriter file = new System.IO.StreamWriter(fname))
|
||||
{
|
||||
file.WriteLine(text);
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
return fname;
|
||||
}
|
||||
|
||||
static void try_write_file_fullpath(string[] filename, string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach(string fname in filename)
|
||||
{
|
||||
using (System.IO.StreamWriter file = new System.IO.StreamWriter(fname))
|
||||
{
|
||||
file.WriteLine(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using spark_excel.grammar;
|
||||
using Antlr4.Runtime;
|
||||
|
||||
using ExcelDna.Integration;
|
||||
using bts.excel.extensions;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
|
||||
public class GeneratorVisitor : ExcelFormulaBaseVisitor<object>
|
||||
{
|
||||
ExcelReference _orig_ref, _current_ref;
|
||||
functions.lib.FunctionLibrary _library;
|
||||
ExcelFormulaErrorListener _error_listener = new ExcelFormulaErrorListener();
|
||||
IDefinitionVisitor _defs;
|
||||
IParameters _params;
|
||||
string _document_name;
|
||||
public GeneratorVisitor(ExcelReference rref, IDefinitionVisitor defs, IParameters parameters, bool include_setups = true)
|
||||
{
|
||||
_current_ref = _orig_ref = rref;
|
||||
_library = new functions.lib.FunctionLibrary(include_setups);
|
||||
_defs = defs;
|
||||
_params = parameters;
|
||||
|
||||
var cref = new CellRef(rref);
|
||||
_document_name = (string)XlCall.Excel(XlCall.xlfGetDocument, 88, cref.SheetName);
|
||||
}
|
||||
|
||||
|
||||
#region Context visitors overrides
|
||||
|
||||
public override object VisitBoolean(ExcelFormulaParser.BooleanContext context)
|
||||
{
|
||||
var expr = context.BOOLEAN();
|
||||
return expr.ToString().ToLower();
|
||||
}
|
||||
|
||||
public override object VisitName(ExcelFormulaParser.NameContext context)
|
||||
{
|
||||
var name = context.GetText();
|
||||
|
||||
var lookup = name;
|
||||
if(!lookup.Contains("!"))
|
||||
lookup = "!" + lookup;
|
||||
object value = XlCall.Excel(XlCall.xlfGetName, lookup);
|
||||
if(value != null && !(value is ExcelError))
|
||||
{
|
||||
string[] par = value.ToString().Split(new[] {'=', 'R', 'C'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
int r = int.Parse(par[0]) - 1;
|
||||
int c = int.Parse(par[1]) - 1;
|
||||
var rref = new ExcelReference(r, r, c, c, _orig_ref.GetSheetName());
|
||||
process_cell(rref, out _);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public override object VisitMain(ExcelFormulaParser.MainContext context)
|
||||
{
|
||||
return context.expr().Accept(this);
|
||||
}
|
||||
|
||||
public override object VisitMulDiv(ExcelFormulaParser.MulDivContext context)
|
||||
{
|
||||
var expr = context.expr();
|
||||
|
||||
var arg1 = expr[0].Accept(this);
|
||||
if (arg1 is ErrorValue)
|
||||
return arg1;
|
||||
|
||||
var arg2 = expr[1].Accept(this);
|
||||
if (arg2 is ErrorValue)
|
||||
return arg2;
|
||||
|
||||
string code = $"{arg1} {context.op.Text} {arg2}";
|
||||
return code;
|
||||
}
|
||||
|
||||
public override object VisitAddSub(ExcelFormulaParser.AddSubContext context)
|
||||
{
|
||||
var expr = context.expr();
|
||||
|
||||
var arg1 = expr[0].Accept(this);
|
||||
if (arg1 is ErrorValue)
|
||||
return arg1;
|
||||
|
||||
var arg2 = expr[1].Accept(this);
|
||||
if (arg2 is ErrorValue)
|
||||
return arg2;
|
||||
|
||||
string code = $"{arg1} {context.op.Text} {arg2}";
|
||||
return code;
|
||||
}
|
||||
|
||||
public override object VisitPercentage(ExcelFormulaParser.PercentageContext context)
|
||||
{
|
||||
var expr = context.expr();
|
||||
|
||||
var arg1 = expr.Accept(this);
|
||||
if (arg1 is ErrorValue)
|
||||
return arg1;
|
||||
|
||||
return $"( {arg1} / 100.0 )";
|
||||
}
|
||||
|
||||
public override object VisitRelational(ExcelFormulaParser.RelationalContext context)
|
||||
{
|
||||
var expr = context.expr();
|
||||
|
||||
var arg1 = expr[0].Accept(this);
|
||||
if (arg1 is ErrorValue)
|
||||
return arg1;
|
||||
|
||||
var arg2 = expr[1].Accept(this);
|
||||
if (arg2 is ErrorValue)
|
||||
return arg2;
|
||||
|
||||
return $"{arg1} {context.op.Text} {arg2}";
|
||||
}
|
||||
|
||||
|
||||
public override object VisitError(ExcelFormulaParser.ErrorContext context)
|
||||
{
|
||||
return new ErrorValue(context.GetText());
|
||||
}
|
||||
|
||||
public override object VisitString(ExcelFormulaParser.StringContext context)
|
||||
{
|
||||
var str = context.GetText();
|
||||
return $"std::string({str})";
|
||||
}
|
||||
|
||||
public override object VisitUnary(ExcelFormulaParser.UnaryContext context)
|
||||
{
|
||||
var expr = context.expr();
|
||||
if(context.sign.Text == "-")
|
||||
return "-" + expr.Accept(this);
|
||||
else
|
||||
return expr.Accept(this);
|
||||
}
|
||||
|
||||
public override object VisitFunction(ExcelFormulaParser.FunctionContext context)
|
||||
{
|
||||
var name = context.name.Text;
|
||||
|
||||
ExcelReference current = _orig_ref;
|
||||
var expr = context.expr();
|
||||
var args = new IExpression[expr.Length];
|
||||
for (var i = 0; i < expr.Length; i++)
|
||||
args[i] = new Expression<object>(expr[i], this);
|
||||
return _library.call_function(name, args, _defs, current);
|
||||
}
|
||||
|
||||
public override object VisitParenthesis(ExcelFormulaParser.ParenthesisContext context)
|
||||
{
|
||||
return context.expr().Accept(this);
|
||||
}
|
||||
|
||||
public override object VisitNumber(ExcelFormulaParser.NumberContext context)
|
||||
{
|
||||
var num = context.GetText();
|
||||
if(double.TryParse(num, out double res))
|
||||
return res.ToString("0.0############");
|
||||
return num;
|
||||
}
|
||||
|
||||
public override object VisitConcatenate(ExcelFormulaParser.ConcatenateContext context)
|
||||
{
|
||||
var expr = context.expr();
|
||||
|
||||
var arg1 = "std::to_string(" + expr[0].Accept(this) + ")";
|
||||
var arg2 = "std::to_string(" + expr[1].Accept(this) + ")";
|
||||
return $"{arg1}+{arg2}";
|
||||
}
|
||||
|
||||
public override object VisitRange(ExcelFormulaParser.RangeContext context)
|
||||
{
|
||||
var rng = context.rng();
|
||||
return rng.Accept(this);
|
||||
}
|
||||
|
||||
public override object VisitRangeList(ExcelFormulaParser.RangeListContext context)
|
||||
{
|
||||
var result = new List<RangeRef>();
|
||||
|
||||
foreach (var range in context.rng())
|
||||
{
|
||||
var obj = range.Accept(this);
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case ErrorValue err:
|
||||
result.Add(null);
|
||||
break;
|
||||
case RangeRef rangeRef:
|
||||
result.Add(rangeRef);
|
||||
break;
|
||||
case null:
|
||||
result.Add(null);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Unexpected type: {obj.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object VisitRangeRef(ExcelFormulaParser.RangeRefContext context)
|
||||
{
|
||||
var obj = context.@ref().Accept(this);
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case ErrorValue _:
|
||||
return obj;
|
||||
case CellRef start:
|
||||
var (column, row) = CellRef.ConvertAddressToCoord(context.end.Text);
|
||||
var rng = new RangeRef(start, column, row);
|
||||
|
||||
if(rng.IsOneCell())
|
||||
{
|
||||
var rref = new ExcelReference(row - 1, row - 1, column - 1, column - 1, rng.SheetName);
|
||||
process_cell(rref, out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
var initdef = $@"
|
||||
auto range_init = [](auto& initval, int nrows, int ncols)
|
||||
{{
|
||||
Range< std::decay_t<decltype((initval))> > rng(nrows, ncols);
|
||||
rng(0,0) = initval;
|
||||
return rng;
|
||||
}};";
|
||||
_defs.add_def(initdef);
|
||||
var loaddef = $@"
|
||||
auto range_set = [](auto& range, auto& val, int r, int c)
|
||||
{{
|
||||
range(r,c) = val;
|
||||
}};";
|
||||
_defs.add_def(loaddef);
|
||||
var vec = rng.get_all_cells();
|
||||
if(vec.Count > 0)
|
||||
{
|
||||
for(int i = 0; i < vec.Count; i++)
|
||||
{
|
||||
int r = (vec[i].Row-1) - (vec[0].Row-1);
|
||||
int c = (vec[i].Column-1) - (vec[0].Column-1);
|
||||
var rref = new ExcelReference(vec[i].Row-1, vec[i].Row-1, vec[i].Column-1, vec[i].Column-1, vec[i].SheetName);
|
||||
var cellref = process_cell(rref, out _);
|
||||
if(i == 0)
|
||||
{
|
||||
string init = $"auto&& {rng.to_name()} = range_init({cellref.ToString()}, {rng.NumRows()}, {rng.NumCols()}); " ;
|
||||
_defs.add_def(init);
|
||||
}
|
||||
else
|
||||
{
|
||||
string next = $"range_set({rng.to_name()}, {cellref.ToString()}, {r}, {c}); ";
|
||||
_defs.add_def(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rng;
|
||||
case null:
|
||||
return null;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Unexpected type: {obj.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
public override object VisitCell(ExcelFormulaParser.CellContext context)
|
||||
{
|
||||
return context.@ref().Accept(this);
|
||||
}
|
||||
|
||||
public override object VisitCellList(ExcelFormulaParser.CellListContext context)
|
||||
{
|
||||
var result = new List<CellRef>();
|
||||
foreach (var @ref in context.@ref())
|
||||
{
|
||||
var obj = @ref.Accept(this);
|
||||
switch (obj)
|
||||
{
|
||||
case ErrorValue _:
|
||||
// TODO: Warn that error has been replaced with null here?
|
||||
result.Add(null);
|
||||
break;
|
||||
case CellRef cellRef:
|
||||
result.Add(cellRef);
|
||||
break;
|
||||
case null:
|
||||
result.Add(null);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Unexpected type: {obj.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object VisitCellRef(ExcelFormulaParser.CellRefContext context)
|
||||
{
|
||||
var colRow = context.colRow.Text;
|
||||
var (col1, row1) = CellRef.ConvertAddressToCoord(colRow);
|
||||
var sheetname = context.sheet?.Text ?? _orig_ref.GetSheetName();
|
||||
|
||||
var rref = new ExcelReference(row1 - 1, row1 - 1, col1 - 1, col1 - 1, sheetname);
|
||||
return process_cell(rref, out _);
|
||||
}
|
||||
|
||||
private object process_cell(ExcelReference rref, out string def)
|
||||
{
|
||||
_current_ref = rref;
|
||||
var cellref = new CellRef(rref);
|
||||
string r1c1 = cellref.r1_c1();
|
||||
|
||||
var formula = XlCall.Excel(XlCall.xlfGetCell, 41, rref);
|
||||
var formstring = formula.ToString();
|
||||
if (string.IsNullOrEmpty(formstring))
|
||||
formstring = "0.0";
|
||||
if (!formstring.StartsWith("="))
|
||||
formstring = "=" + formstring;
|
||||
|
||||
var cellname = XlCall.Excel(XlCall.xlfGetDef, r1c1, cellref.SheetName);
|
||||
bool named = false;
|
||||
if (!(cellname is ExcelError))
|
||||
{
|
||||
named = true;
|
||||
cellref.named_range = cellname.ToString();
|
||||
}
|
||||
|
||||
var antlrstream = new AntlrInputStream(formstring);
|
||||
var lexer = new ExcelFormulaLexer(antlrstream);
|
||||
var tokens = new CommonTokenStream(lexer);
|
||||
var parser = new ExcelFormulaParser(tokens);
|
||||
parser.AddErrorListener(_error_listener);
|
||||
var parsetree = parser.main();
|
||||
if (!_error_listener.IsValid)
|
||||
throw new Exception(_error_listener.offendingSymbol + _error_listener.msg, _error_listener.exception);
|
||||
else
|
||||
{
|
||||
def = $"auto&& {cellref.ToString()} = {new GeneratorVisitor(rref, _defs, _params).VisitMain(parsetree)} ;";
|
||||
_defs.add_def(def);
|
||||
if (named && _params.algo_params.ContainsKey(cellref.named_range))
|
||||
{
|
||||
_defs.map_def(def, $"auto&& {cellref.named_range} = me->{cellref.named_range};");
|
||||
}
|
||||
if (named && _params.special_params.ContainsKey(cellref.named_range))
|
||||
{
|
||||
string realname = cellref.named_range;
|
||||
_params.special_params.TryGetValue(cellref.named_range, out realname);
|
||||
_defs.map_def(def, $"auto&& {cellref.named_range} = get_{realname}();");
|
||||
}
|
||||
}
|
||||
return cellref;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public class RangeRef
|
||||
{
|
||||
public RangeRef(CellRef start, CellRef end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
public string named_range { get; set;}
|
||||
|
||||
public RangeRef(CellRef start, int endColumn, int endRow)
|
||||
: this(start, new CellRef(start.SheetName, (endColumn, endRow)))
|
||||
{ }
|
||||
|
||||
public RangeRef Offset(int columnOffset, int rowOffset)
|
||||
{
|
||||
var start = Start.Offset(columnOffset, rowOffset);
|
||||
var end = End.Offset(columnOffset, rowOffset);
|
||||
return new RangeRef(start, end);
|
||||
}
|
||||
|
||||
public (int col, int row) compute_indexes(int col, int row)
|
||||
{
|
||||
return (col - Start.Column, row - Start.Row);
|
||||
}
|
||||
|
||||
public CellRef Start { get; }
|
||||
|
||||
public CellRef End { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return to_name();
|
||||
}
|
||||
|
||||
public string SheetName => Start.SheetName;
|
||||
public string SimpleSheetName => Start.SimpleSheetName;
|
||||
|
||||
public string to_address()
|
||||
{
|
||||
return string.Format("'{0}'!{1}:{2}",
|
||||
SheetName,
|
||||
CellRef.ConvertCoordToAddress((Start.Column, Start.Row)),
|
||||
CellRef.ConvertCoordToAddress((End.Column, End.Row)));
|
||||
}
|
||||
|
||||
public string to_name()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(named_range))
|
||||
return named_range;
|
||||
else
|
||||
{
|
||||
return string.Format("{0}_{1}_{2}",
|
||||
Start.SimpleSheetName,
|
||||
CellRef.ConvertCoordToAddress((Start.Column, Start.Row)),
|
||||
CellRef.ConvertCoordToAddress((End.Column, End.Row)));
|
||||
}
|
||||
}
|
||||
|
||||
public string r1_c1()
|
||||
{
|
||||
return $"{Start.r1_c1()}:{End.r1_c1()}";
|
||||
}
|
||||
|
||||
public bool IsVector()
|
||||
{
|
||||
return IsSingleRow() || IsSingleColumn();
|
||||
}
|
||||
|
||||
public bool IsOneCell()
|
||||
{
|
||||
return IsSingleRow() && IsSingleColumn();
|
||||
}
|
||||
|
||||
internal List<CellRef> get_all_cells()
|
||||
{
|
||||
var result = new List<CellRef>();
|
||||
var sheetName = Start.SheetName;
|
||||
var numRows = NumRows();
|
||||
var lastRow = Start.Row + numRows - 1;
|
||||
|
||||
for (var row = Start.Row; row <= lastRow; row++)
|
||||
{
|
||||
var numCols = NumCols();
|
||||
var lastCol = Start.Column + numCols - 1;
|
||||
for (var col = Start.Column; col <= lastCol; col++)
|
||||
{
|
||||
result.Add(new CellRef(sheetName, (col, row)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal CellRef[,] cell_matrix_col_row()
|
||||
{
|
||||
var numCols = NumCols();
|
||||
var numRows = NumRows();
|
||||
var result = new CellRef[numCols, numRows];
|
||||
var sheetName = Start.SheetName;
|
||||
|
||||
var lastCol = Start.Column + numCols - 1;
|
||||
var lastRow = Start.Row + numRows - 1;
|
||||
int c = 0;
|
||||
for (var col = Start.Column; col <= lastCol; col++)
|
||||
{
|
||||
int r = 0;
|
||||
for (var row = Start.Row; row <= lastRow; row++)
|
||||
{
|
||||
result[c, r++] = new CellRef(sheetName, (col, row));
|
||||
}
|
||||
c++; //it's haunting me
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal List<CellRef> GetVector()
|
||||
{
|
||||
Debug.Assert(IsVector());
|
||||
|
||||
var result = new List<CellRef>();
|
||||
|
||||
if (IsSingleColumn())
|
||||
{
|
||||
var col = Start.Column;
|
||||
var sheetName = Start.SheetName;
|
||||
var numRows = NumRows();
|
||||
var lastRow = Start.Row + numRows - 1;
|
||||
for (var row = Start.Row; row <= lastRow; row++)
|
||||
{
|
||||
result.Add(new CellRef(sheetName, (col, row)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(IsSingleRow());
|
||||
|
||||
var row = Start.Row;
|
||||
var sheetName = Start.SheetName;
|
||||
var numCols = NumCols();
|
||||
var lastCol = Start.Column + numCols - 1;
|
||||
for (var col = Start.Column; col <= lastCol; col++)
|
||||
{
|
||||
result.Add(new CellRef(sheetName, (col, row)));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsSingleColumn()
|
||||
{
|
||||
Debug.Assert(Start.SheetName == End.SheetName);
|
||||
return Start.Column == End.Column;
|
||||
}
|
||||
|
||||
public bool IsSingleRow()
|
||||
{
|
||||
Debug.Assert(Start.SheetName == End.SheetName);
|
||||
return Start.Row == End.Row;
|
||||
}
|
||||
|
||||
public int NumRows()
|
||||
{
|
||||
Debug.Assert(Start.SheetName == End.SheetName);
|
||||
return End.Row - Start.Row + 1;
|
||||
}
|
||||
|
||||
public int NumCols()
|
||||
{
|
||||
Debug.Assert(Start.SheetName == End.SheetName);
|
||||
return End.Column - Start.Column + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using bts.excel.functions;
|
||||
using ExcelDna.Registration;
|
||||
using ExcelDna.Integration;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.excel.functions.lib
|
||||
{
|
||||
public interface IFunction
|
||||
{
|
||||
object function(IExpression[] args, IDefinitionVisitor dv, ExcelReference containing_cell);
|
||||
}
|
||||
public class FunctionLibrary
|
||||
{
|
||||
private readonly Dictionary<string, IFunction> _functions =
|
||||
new Dictionary<string, IFunction>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, SparkAlgoCodeAttribute> _dnafunctions =
|
||||
new Dictionary<string, SparkAlgoCodeAttribute>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public FunctionLibrary(bool include_setups)
|
||||
{
|
||||
add_functions(Logical.functions);
|
||||
add_functions(Mathematics.functions);
|
||||
add_functions(Stat.functions);
|
||||
if(include_setups)
|
||||
add_function(nameof(sparkalgo_setup_order), new sparkalgo_setup_order());
|
||||
|
||||
var dna = ExcelRegistration.GetExcelFunctions(); string fn = string.Empty;
|
||||
foreach (var xl in dna)
|
||||
{
|
||||
foreach(var att in xl.CustomAttributes)
|
||||
{
|
||||
if(att is SparkAlgoCodeAttribute ac)
|
||||
_dnafunctions[xl.FunctionAttribute.Name] = ac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool is_defined(string name) => _functions.ContainsKey(name) || _dnafunctions.ContainsKey(name);
|
||||
|
||||
public void add_function(string name, IFunction function)
|
||||
{
|
||||
_functions.Add(name, function);
|
||||
}
|
||||
|
||||
public void add_functions(Dictionary<string, IFunction> delegates)
|
||||
{
|
||||
foreach (var f in delegates)
|
||||
_functions.Add(f.Key, f.Value);
|
||||
}
|
||||
|
||||
public object call_function(string name, IExpression[] args, IDefinitionVisitor dv, ExcelReference containing_cell)
|
||||
{
|
||||
object code = string.Empty;
|
||||
if(string.IsNullOrEmpty(name) || string.IsNullOrWhiteSpace(name))
|
||||
return code;
|
||||
if (_functions.TryGetValue(name, out IFunction func))
|
||||
code = func.function(args, dv, containing_cell);
|
||||
else
|
||||
code = bts_function_or_throw(name, args, dv, containing_cell);
|
||||
return code;
|
||||
}
|
||||
|
||||
object bts_function_or_throw(string name, IExpression[] args, IDefinitionVisitor dv, ExcelReference containing_cell)
|
||||
{
|
||||
SparkAlgoCodeAttribute att = null;
|
||||
if (_dnafunctions.TryGetValue(name, out att))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(att.dual_definition))
|
||||
dv.add_dual_definition(att.dual_definition);
|
||||
if (!string.IsNullOrEmpty(att.dual_impl))
|
||||
dv.add_dual_impl(att.dual_impl);
|
||||
if(!string.IsNullOrEmpty(att.replacement_call))
|
||||
{
|
||||
return att.replacement_call;
|
||||
}
|
||||
else
|
||||
{
|
||||
object[] param = args.Select(x => x.evaluate()).ToArray();
|
||||
string function_call = $"{name} ( "; int z = 0;
|
||||
foreach (var p in param)
|
||||
function_call += p.ToString() + (z++ < param.Length - 1 ? ", " : "");
|
||||
function_call += ")";
|
||||
return modify_if_array_formula(function_call, containing_cell);
|
||||
}
|
||||
}
|
||||
throw new NotSupportedException("Function " + name + " not currently supported by algogen");
|
||||
}
|
||||
|
||||
internal bool is_array_formula(ExcelReference containing_cell)
|
||||
{
|
||||
bool is_array_formula = (bool)XlCall.Excel(XlCall.xlfGetCell, 49, containing_cell);
|
||||
return is_array_formula;
|
||||
}
|
||||
|
||||
internal object modify_if_array_formula(object code, ExcelReference containing_cell)
|
||||
{
|
||||
if(!is_array_formula(containing_cell))
|
||||
return code;
|
||||
using (var ech = new ExcelEchoOffHelper())
|
||||
{
|
||||
using (var calc = new ExcelCalculationManualHelper())
|
||||
{
|
||||
using (var sel = new ExcelSelectionHelper(containing_cell))
|
||||
{
|
||||
lock(this)
|
||||
{
|
||||
var formula = XlCall.Excel(XlCall.xlfGetCell, 41, containing_cell);
|
||||
var c = new CellRef(containing_cell);
|
||||
var a = XlCall.Excel(XlCall.xlcSelectSpecial, 6);
|
||||
ExcelReference array = (ExcelReference)XlCall.Excel(XlCall.xlfSelection);
|
||||
code += $"({containing_cell.RowFirst - array.RowFirst},{containing_cell.ColumnFirst - array.ColumnFirst})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
||||
public class SparkAlgoCodeAttribute : Attribute
|
||||
{
|
||||
public string dual_definition;
|
||||
public string dual_impl;
|
||||
public string replacement_call;
|
||||
|
||||
public SparkAlgoCodeAttribute()
|
||||
{
|
||||
//nulls ok
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using ExcelDna.Integration;
|
||||
using bts.api;
|
||||
using ExcelDna.Registration.Utils;
|
||||
using System.Reactive.Linq;
|
||||
using bts.md;
|
||||
|
||||
using MD = bts.excel.rtd.MDSubscribe;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public class SparkAlgoFunctions
|
||||
{
|
||||
static ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>();
|
||||
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_parameter([ExcelArgument(AllowReference = true)] object parameter, string type)
|
||||
{
|
||||
ExcelReference par = (ExcelReference)parameter;
|
||||
string sheetname = (string)XlCall.Excel(XlCall.xlSheetNm, par);
|
||||
var cellref = new CellRef(sheetname, (par.ColumnFirst + 1, par.RowFirst + 1));
|
||||
string r1c1 = cellref.r1_c1();
|
||||
var cellname = XlCall.Excel(XlCall.xlfGetDef, r1c1, sheetname);
|
||||
if (!(cellname is ExcelError))
|
||||
{
|
||||
BTSRibbon.globals.register_algo_parameter(type ?? "double", cellname.ToString());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return $"{ErrorValue.Name}, please make algo parameters named ranges";
|
||||
}
|
||||
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_unregister_parameter(string parameter)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(parameter))
|
||||
{
|
||||
BTSRibbon.globals.unregister_algo_parameter(parameter);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return $"{ErrorValue.Name}, no parameter named " + parameter;
|
||||
}
|
||||
|
||||
//See ExcelAlgoTemplate.cpp __AlgoName__ class def
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_user_parameter([ExcelArgument(AllowReference = true)] object parameter)
|
||||
{
|
||||
return sparkalgo_register_special_parameter(parameter, "user");
|
||||
}
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_account_parameter([ExcelArgument(AllowReference = true)] object parameter)
|
||||
{
|
||||
return sparkalgo_register_special_parameter(parameter, "account");
|
||||
}
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_side_parameter([ExcelArgument(AllowReference = true)] object parameter)
|
||||
{
|
||||
return sparkalgo_register_special_parameter(parameter, "side");
|
||||
}
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_price_parameter([ExcelArgument(AllowReference = true)] object parameter)
|
||||
{
|
||||
return sparkalgo_register_special_parameter(parameter, "price");
|
||||
}
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_quantity_parameter([ExcelArgument(AllowReference = true)] object parameter)
|
||||
{
|
||||
return sparkalgo_register_special_parameter(parameter, "quantity");
|
||||
}
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_tif_parameter([ExcelArgument(AllowReference = true)] object parameter)
|
||||
{
|
||||
return sparkalgo_register_special_parameter(parameter, "time_in_force");
|
||||
}
|
||||
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_register_special_parameter([ExcelArgument(AllowReference = true)] object parameter, string name)
|
||||
{
|
||||
ExcelReference par = (ExcelReference)parameter;
|
||||
string sheetname = (string)XlCall.Excel(XlCall.xlSheetNm, par);
|
||||
var cellref = new CellRef(sheetname, (par.ColumnFirst + 1, par.RowFirst + 1));
|
||||
string r1c1 = cellref.r1_c1();
|
||||
var cellname = XlCall.Excel(XlCall.xlfGetDef, r1c1, sheetname);
|
||||
if (!(cellname is ExcelError))
|
||||
{
|
||||
if(string.IsNullOrEmpty(name))
|
||||
name = cellname.ToString();
|
||||
BTSRibbon.globals.register_special_parameter(cellname.ToString(), name);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return $"{ErrorValue.Name}, please make special parameters named ranges";
|
||||
}
|
||||
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public object sparkalgo_unregister_special_parameter(string parameter)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(parameter))
|
||||
{
|
||||
BTSRibbon.globals.unregister_special_parameter(parameter);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return $"{ErrorValue.Name}, no special parameter named " + parameter;
|
||||
}
|
||||
|
||||
[ExcelFunction]
|
||||
[SparkAlgoCode]
|
||||
static public object sparkalgo_take_snapshot(string tag, object data)
|
||||
{
|
||||
object val;
|
||||
if(!_cache.TryGetValue(tag, out val))
|
||||
{
|
||||
_cache[tag] = val = data;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
[ExcelFunction()]
|
||||
[SparkAlgoCode(
|
||||
dual_definition = @"
|
||||
int sparkalgo_snapshot_switch(const std::string& name, int on_off);",
|
||||
dual_impl = @"
|
||||
int __AlgoName__::sparkalgo_snapshot_switch(const std::string& name, int on_off)
|
||||
{
|
||||
auto tag = name+""_switch"";
|
||||
if(on_off)
|
||||
return sparkalgo_take_snapshot(tag, on_off);
|
||||
else
|
||||
{
|
||||
auto it = me->snapshots.find(tag);
|
||||
if(it == me->snapshots.end())
|
||||
return on_off;
|
||||
else
|
||||
return std::get<int>(it->second);
|
||||
}
|
||||
}"
|
||||
)]
|
||||
static public object sparkalgo_snapshot_switch(string tag, int on_off)
|
||||
{
|
||||
object val;
|
||||
if (!_cache.TryGetValue(tag, out val) && on_off > 0)
|
||||
{
|
||||
_cache[tag] = val = on_off;
|
||||
}
|
||||
return val ?? 0;
|
||||
}
|
||||
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public void sparkalgo_reset_all_snapshots(object _)
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
static public void sparkalgo_reset_all_snapshots_no_args()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
[ExcelFunction(IsMacroType = true)]
|
||||
static public void sparkalgo_reset_snapshot(string tag)
|
||||
{
|
||||
_cache.TryRemove(tag, out object val);
|
||||
}
|
||||
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Resolve a security id from contract details")]
|
||||
[SparkAlgoCode(
|
||||
replacement_call = "sparkalgo_resolve_security_id()"
|
||||
)]
|
||||
public async static void sparkalgo_resolve_primary_security_id(
|
||||
[ExcelArgument("Required. The specific contract, GCZ8 or ZNU8-ZNZ8, etc")] string contract,
|
||||
[ExcelArgument("Required. The symbol, GC or ZN, etc")] string symbol,
|
||||
[ExcelArgument("Optional. The type, Future or Spread, default = Future")] string security_type,
|
||||
[ExcelArgument("Optional. The exchange, default = GLBX")] string exchange,
|
||||
ExcelAsyncHandle handle)
|
||||
{
|
||||
exchange = string.IsNullOrEmpty(exchange) ? Security.ExchangeDataOneofCase.Glbx.ToString() : exchange;
|
||||
Func<SecurityImmutable, bool> where = MD.md_where(contract, symbol, security_type, exchange, out Venue ven);
|
||||
var md = bts.excel.rtd.MDSubscribe.market_data(ven);
|
||||
try
|
||||
{
|
||||
SecurityImmutable? sec = await md.find_security_async(where, TimeSpan.FromSeconds(15));
|
||||
if (sec != null && sec.HasValue)
|
||||
handle.SetResult(sec.Value.Id);
|
||||
else
|
||||
handle.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
handle.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Resolve a security id from contract details")]
|
||||
[SparkAlgoCode()]
|
||||
public async static void sparkalgo_resolve_security_id(
|
||||
[ExcelArgument("Required. The specific contract, GCZ8 or ZNU8-ZNZ8, etc")] string contract,
|
||||
[ExcelArgument("Required. The symbol, GC or ZN, etc")] string symbol,
|
||||
[ExcelArgument("Optional. The type, Future or Spread, default = Future")] string security_type,
|
||||
[ExcelArgument("Optional. The exchange, default = GLBX")] string exchange,
|
||||
ExcelAsyncHandle handle)
|
||||
{
|
||||
exchange = string.IsNullOrEmpty(exchange) ? Security.ExchangeDataOneofCase.Glbx.ToString() : exchange;
|
||||
Func<SecurityImmutable, bool> where = MD.md_where(contract, symbol, security_type, exchange, out Venue ven);
|
||||
var md = bts.excel.rtd.MDSubscribe.market_data(ven);
|
||||
try
|
||||
{
|
||||
SecurityImmutable? sec = await md.find_security_async(where, TimeSpan.FromSeconds(15));
|
||||
if (sec != null && sec.HasValue)
|
||||
handle.SetResult(sec.Value.Id);
|
||||
else
|
||||
handle.SetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
handle.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Subscribe to BTS Spark full depth market data by security_id")]
|
||||
[SparkAlgoCode(
|
||||
dual_definition = @"
|
||||
ExcelBookAccessor sparkalgo_md_book_id(SecurityKey sk);",
|
||||
dual_impl = @"
|
||||
ExcelBookAccessor __AlgoName__::sparkalgo_md_book_id(SecurityKey sk)
|
||||
{
|
||||
ExcelBookAccessor acc(me->last_book);
|
||||
return acc;
|
||||
}"
|
||||
)]
|
||||
public static object sparkalgo_md_book_id([ExcelArgument("Required. The security_id")] long security_id)
|
||||
{
|
||||
|
||||
var param = new object[] { security_id };
|
||||
return ObservableRtdUtil.Observe(nameof(sparkalgo_md_book_id), param,
|
||||
() => MD.md_observable_id(Convert.ToUInt32(security_id)).Select(data =>
|
||||
{
|
||||
var x = data.FirstOrDefault();
|
||||
if (x != null && x is IMDBook upd)
|
||||
return rtd.BookMDFormatter.format(upd);
|
||||
return x;
|
||||
}));
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS")]
|
||||
[SparkAlgoCode(
|
||||
dual_impl = @"
|
||||
OrderKey __AlgoName__::sparkalgo_generate_local_orderid()
|
||||
{
|
||||
static uint32_t id = 0;
|
||||
return OrderKey( ++id );
|
||||
}"
|
||||
)]
|
||||
public static object sparkalgo_generate_local_orderid(object dependency)
|
||||
{
|
||||
ExcelReference caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference;
|
||||
CellRef rref = new CellRef(caller);
|
||||
return rref.Row*1000 + rref.Column;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using spark_excel.grammar;
|
||||
using Antlr4.Runtime;
|
||||
using ExcelDna.Integration;
|
||||
|
||||
namespace bts.excel.functions.lib
|
||||
{
|
||||
public class sparkalgo_setup_order : IFunction
|
||||
{
|
||||
public object function(IExpression[] args, IDefinitionVisitor dv, ExcelDna.Integration.ExcelReference containing_cell)
|
||||
{
|
||||
CodeParts _parts = new CodeParts();
|
||||
var gen = new GeneratorVisitor(containing_cell, _parts, BTSRibbon.globals, false);
|
||||
|
||||
var formula = XlCall.Excel(XlCall.xlfGetCell, 41, containing_cell);
|
||||
|
||||
var antlrstream = new AntlrInputStream(formula.ToString());
|
||||
var lexer = new ExcelFormulaLexer(antlrstream);
|
||||
var tokens = new CommonTokenStream(lexer);
|
||||
var parser = new ExcelFormulaParser(tokens);
|
||||
|
||||
var parsetree = parser.main();
|
||||
|
||||
var name = new CellRef(containing_cell).ToString();
|
||||
var expr = gen.VisitMain(parsetree);
|
||||
|
||||
foreach (var dd in _parts.get_dual_definitions())
|
||||
dv.add_dual_definition(dd);
|
||||
foreach (var di in _parts.get_dual_impls())
|
||||
dv.add_dual_impl(di);
|
||||
|
||||
string setup = string.Join(Environment.NewLine, _parts.get_defs());
|
||||
setup += $"{Environment.NewLine} me->{name}_order_id = { expr.ToString() };";
|
||||
dv.register_order_setup(setup, $"me->{name}_order_id = cache_attach(attached_order, st);");
|
||||
|
||||
dv.register_algo_parameter("OrderKey", name + "_order_id");
|
||||
return "me->" + name + "_order_id";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.excel.functions.lib
|
||||
{
|
||||
public static class Logical
|
||||
{
|
||||
public static Dictionary<string, IFunction> functions { get; }
|
||||
|
||||
static Logical()
|
||||
{
|
||||
functions = new Dictionary<string, IFunction>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"AND", new And()},
|
||||
{"IF", new If()},
|
||||
{"IFERROR", new IfError()},
|
||||
{"OR", new Or()},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mathematics
|
||||
{
|
||||
public static Dictionary<string, IFunction> functions { get; }
|
||||
|
||||
static Mathematics()
|
||||
{
|
||||
functions = new Dictionary<string, IFunction>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"LN", new Ln()},
|
||||
{"LOG", new Log()},
|
||||
{"POWER", new Power()},
|
||||
{"PI", new Pi()},
|
||||
{"SQRT", new Sqrt()},
|
||||
{"SUM", new Sum()},
|
||||
{"SIN", new Sin()},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class Stat
|
||||
{
|
||||
public static Dictionary<string, IFunction> functions { get; }
|
||||
|
||||
static Stat()
|
||||
{
|
||||
functions = new Dictionary<string, IFunction>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"AVERAGE", new Average()},
|
||||
{"MAX", new Max()},
|
||||
{"MIN", new Min()},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace bts.excel.functions.lib
|
||||
{
|
||||
public class And : IFunction
|
||||
{
|
||||
public object function(IExpression[] args, IDefinitionVisitor dv, ExcelDna.Integration.ExcelReference containing_cell)
|
||||
{
|
||||
// Syntax: AND(logical1, [logical2], ...)
|
||||
// https://support.office.com/en-gb/article/AND-function-5f19b2e8-e1df-4408-897a-ce285a19e9d9
|
||||
|
||||
if (args.Length < 2)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"AND function requires at least 2 arguments, got {args.Length}",
|
||||
nameof(args));
|
||||
}
|
||||
|
||||
string code = "(";
|
||||
foreach (var fx in args)
|
||||
{
|
||||
var arg = fx.evaluate();
|
||||
|
||||
if (arg is ErrorValue)
|
||||
return arg;
|
||||
code += arg + " && ";
|
||||
}
|
||||
code += "true)";
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user