Compare commits

..
Author SHA1 Message Date
John Colvin d421fdb41f Checkpoint - make pnl grid settings pretty (should rebase/clean up) 2019-10-21 12:00:47 -05:00
John Colvin 6cbff72bc3 make time and sales settings pretty 2019-10-19 18:04:39 -05:00
John Colvin d81f5b87fc fix pretty (again damnit) 2019-10-19 18:02:55 -05:00
John Colvin fe6f4ffa92 make mview settings pretty 2019-10-19 17:55:50 -05:00
John Colvin 7841faafa2 hook up HyperlinkColumn.font_weight binding 2019-10-19 17:54:47 -05:00
John Colvin 89df89db11 HyperlinkColumn should bind to HorizontalContentAlignment prop (not HorizontalAlignment) 2019-10-19 17:54:32 -05:00
John Colvin 03867914cb fix WPFHelper.measure_visual_content - 'left edge' calculation for right align; 'bottom edge' for bottom align 2019-10-19 17:53:10 -05:00
John Colvin 52f62c19a4 FillsVM state doesn't need to be observable (every usage is equiv to Observable.Return) 2019-10-19 17:32:14 -05:00
John Colvin 860bda388f ensure latest column settings retrieved from GridSettingsManager 2019-10-19 17:32:14 -05:00
John Colvin 0b73e2744e FillsVM invalidates rows when settings changes (refreshes view) 2019-10-19 17:32:14 -05:00
John Colvin e5d23a987e hide column background color in Fills settings dialog (row bg indicates side) 2019-10-19 17:32:14 -05:00
John Colvin 6ac3eeca2e make fills grid settings pretty 2019-10-19 17:32:14 -05:00
John Colvin 3b73ba25d2 add DefaultConverter prop to ValueMap 2019-10-19 17:32:14 -05:00
John Colvin 34eed73af5 GlobalAlertsView - get rid of sound preview button (play when dropdown closes instead) 2019-10-19 17:32:14 -05:00
John Colvin 2f1f1c007c SP-247 Order Entry Sounds 2019-10-19 17:32:13 -05:00
John Colvin cac4f5da37 OrderBook actually uses default settings by default 2019-10-19 17:32:13 -05:00
John Colvin 58dab25b89 OrderTypeItem template uses Rectangle instead of Badge (the latter has some unresolved rounding issues) 2019-10-19 17:32:13 -05:00
John Colvin 7d100da9b1 GlyphContext - drop unneccesary namespace prefixes 2019-10-19 17:32:13 -05:00
John Colvin c389a47a4a OrderTypeColumn uses a little padding 2019-10-19 17:32:13 -05:00
John Colvin d3d069cf17 fix typo 'ImmutableFontWeight' -> 'ImmutableFontColor' 2019-10-19 17:32:13 -05:00
John Colvin 9576122aeb hook up grid background setting in orderbook 2019-10-19 17:32:13 -05:00
John Colvin a32b40bef6 DIYGrid CellRender.draw_text with layout rounding 2019-10-19 17:32:13 -05:00
John Colvin d9f459ccb5 add TitleCaseConverter/Template to common xaml resources 2019-10-19 17:32:13 -05:00
John Colvin 18bdeea765 add SystemColors palette to color picker gui 2019-10-19 17:32:13 -05:00
John Colvin 87bf9a37cc make GridSettinngs pretty again (starting with OrderBook) 2019-10-19 17:32:13 -05:00
John Colvin 96b6c0fc1d add TypeConverterWrapper 2019-10-19 17:32:12 -05:00
John Colvin 0cee53e287 ReadOnlySelectorWrapper for IList 2019-10-19 17:29:24 -05:00
John Colvin db135a36d9 add 'SelectItemOnDrop' prop to DragDropListBox 2019-10-19 17:29:24 -05:00
John Colvin bd8dbe0071 add general purpose ParameterVMs (immediate use in new GridSettings design) 2019-10-19 17:29:24 -05:00
John Colvin 4f0a01175e OrderBook settings migration rule to enable background color 2019-10-19 17:29:24 -05:00
John Colvin 1f8af68b60 IOrderBookGridSettingsMigrationRule impl in UserSettingsHub 2019-10-19 17:29:24 -05:00
John Colvin 5f2a2a7a56 remove default_vm from ShellSettingsDialogVM (vestige of an earlier time) 2019-10-19 17:29:24 -05:00
John Colvin 6a0e00222c move SystemFontFamilies/Weights lists from FontPicker -> TextUtils 2019-10-19 17:29:24 -05:00
John Colvin c087999c4b add general purpose DIYGridColumn that can be defined directly in xaml 2019-10-19 17:29:24 -05:00
John Colvin 763f8f006d Background TemplateBinding for ExpanderToggleControl 2019-10-19 17:29:24 -05:00
John Colvin 32257f257e strongly type AddRange extensions for IList<T> 2019-10-19 17:29:24 -05:00
John Colvin 4903545642 GridSettings default_font_color shouldn't be transparent 2019-10-19 17:29:24 -05:00
John Colvin 6eb3cf5cf6 better look add button in global alerts dialog 2019-10-19 17:29:24 -05:00
John Colvin cdcd0d0436 get rid of dialog skirt border in global alerts dialog 2019-10-19 17:29:24 -05:00
John Colvin e04ac0592b add LargeMenuItem/Button styles (prereq for new color picker) 2019-10-19 17:29:24 -05:00
John Colvin 0a3cbc8539 AddBox icon 2019-10-19 17:29:24 -05:00
John Colvin fb2385041c add VisualState template/proxy dependency props to support visual state shared style resources 2019-10-19 17:29:24 -05:00
John Colvin 98685e59b3 MouseStateDefinition special impl for Button/MenuItem 'IsPressed' props 2019-10-19 17:29:24 -05:00
John Colvin bf9e6a645a add TabularColorPicker/ColorPickerSuite (more user friendly color gui) 2019-10-19 17:29:24 -05:00
John Colvin 6d02d9a508 ColorPickerControl SelectedColorChanged event 2019-10-19 17:29:23 -05:00
John Colvin 2a8a459cdd ColorPickerControl should not automatically bind its DataContext 2019-10-19 17:29:23 -05:00
John Colvin e25d039f3b add ButtonStyle prop to PopupControl 2019-10-19 17:29:23 -05:00
John Colvin 5cdaa60b86 assert_style_type shouldn't require exact type match, just needs to be assignable 2019-10-19 17:29:23 -05:00
John Colvin ff33d0d735 move ReverseComparer to spark_core 2019-10-19 17:29:23 -05:00
John Colvin 8ba1c7a602 refactor ComparerBuilder.add -> Add to support object initializer syntax 2019-10-19 17:29:23 -05:00
John Colvin 8ef5dd56ee remove unused FontFamily enum 2019-10-19 17:29:23 -05:00
John Colvin c3c1a33081 AlgoStudio - remove algo name from buttons, dock save/revert on bottom 2019-10-19 17:29:23 -05:00
John Colvin 4c0461311f AlgoStudio - hide vertical scrollbar 2019-10-19 17:29:23 -05:00
189 changed files with 6453 additions and 4140 deletions

No files matched your search

@@ -3,10 +3,6 @@ title = "Glossary of Terms"
weight = 9
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Definitions of common terms used in BTS Spark and this manual"
+++
+7 -7
View File
@@ -3,12 +3,12 @@ title = "Help"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Report a problem or find the activity log from the Help module in BTS Spark"
+++
{{ youtube(id="yGQfr_puT3w") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGQfr_puT3w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
## Report a Problem
If there is any issue at all, you can click here
@@ -18,14 +18,14 @@ page_description = "Report a problem or find the activity log from the Help modu
send, BTS will get an alert of everything you submitted and will be
in contact with you shortly.
{{ image(name="image58.png", alt="screenshot") }}
![](image58.png)
## About Spark
Upon launching this, you will see an info window
which will list what version you are running as well as who is
currently logged in.
{{ image(name="about_spark.png", alt="screenshot") }}
![](about_spark.png)
## Activity log
This is an overall log viewer which will show
@@ -34,4 +34,4 @@ page_description = "Report a problem or find the activity log from the Help modu
messages in a text box in the right top corner.
 
{{ image(name="image39.png", alt="screenshot") }}
![](image39.png)
@@ -1,15 +1,15 @@
+++
title = "Launcher"
title = "Module Launcher"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The basics of the BTS Spark launcher and user interface"
+++
{{ youtube(id="OxVqQBKFIDw") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/OxVqQBKFIDw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- The module launcher is the first item that will come up after
logging in to Spark.
@@ -18,7 +18,7 @@ page_description = "The basics of the BTS Spark launcher and user interface"
each module
- It appears like the following:
{{ image(name="image38.png", alt="screenshot") }}
![](image38.png)
- If you right-click anywhere on the launcher you can modify
various display settings including:
@@ -29,7 +29,7 @@ page_description = "The basics of the BTS Spark launcher and user interface"
- Ability to exit the front end, this can also be done by simply clicking the “X” on the top right of this window or by clicking on exit from this options window
- These options are all displayed like the following:
{{ image(name="image68.png", alt="screenshot") }}
![](image68.png)
## Spark User interface
@@ -3,21 +3,17 @@ title = "Logging in to the system"
weight = 1
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "How to log into BTS Spark"
+++
The shortcut icon will appear on your desktop like below:
{{ image(name="image64.png", alt="BTS icon") }}
![](image64.png)
Simply double click or single click and hit enter to launch the
login screen:
{{ image(name="image3.png", alt="login screen") }}
![](image3.png)
Choose your username from the drop down, choose your initial
workspace, enter your password, and click Login
+22 -20
View File
@@ -3,22 +3,22 @@ title = "Market"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The BTS Spark Market module contains the Market View and Time and Sales windows"
+++
- This is one of the main items on the module launcher
- When hovering over it, it will show 2 different modules which can
be launched from it, market view and then, time and sales
- It appears like the below:
{{ image(name="image53.png", alt="screenshot") }}
![](image53.png)
## Market View
{{ youtube(id="Y54S1uscT_g") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y54S1uscT_g" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- This provides a grid of outright, spread, and synthetic products
showing real-time market data all in one place
@@ -29,7 +29,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- You choose which products you would like to view upon launching it,
the security browser will appear as shown below:
{{ image(name="image21.png", alt="security picker") }}
![](image21.png)
- Click on individual securities that you wish to view and click add,
they will then show up in the selected securities window below.  If
@@ -45,13 +45,13 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- Below is an example of an outright, spread, all futures, and
synthetic chosen:
{{ image(name="image14.png", alt="screenshot") }}
![](image14.png)
- Clicking OK will bring up the Market View with the selected
securities you have chosen, in this example, it appears like the
following:
{{ image(name="market_view.png", alt="screenshot") }}
![](market_view.png)
- Once a market view is launched and you are viewing data, you can
customize the view further by the following:
@@ -61,19 +61,19 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- Filtering - by hovering over the column header, a filtering box
will appear like below:
{{ image(name="image66.png", alt="screenshot") }}
![](image66.png)
- All columns let you sort by that columns minimum and maximum
information in a box that appears like the following:
{{ image(name="image24.png", alt="screenshot") }}
![](image24.png)
- Specifically for the securities column, there is a search box that
will appear by bringing up the filtering box, which enables users to
type the security in which they are looking for.  This appears like
the following:
{{ image(name="image11.png", alt="screenshot") }}
![](image11.png)
- Besides viewing the market data, you can do a lot within the market
view by utilizing the icons to the left of the main window, these
@@ -88,7 +88,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- Change securities
- Click on the settings icon {{ image(name="change_securities.png", alt="screenshot") }} which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Click on the settings icon ![](change_securities.png) which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Launch an order ticket
- This is done 1 of 3 ways:
@@ -96,7 +96,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
or Ask Qty which will bring up an order ticket pre-populated with
either the Bid or Ask price and volume.
- Highlighting a single security and clicking on the order ticket
{{ image(name="image23.png", alt="screenshot") }} icon
![](image23.png) icon
on the left-hand browser window of the market view
- Highlighting a single security and right clicking bringing up the
menu options to launch an order ticket.
@@ -121,7 +121,7 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- This is done by either highlighting 1 or many securities and then
clicking on the Fills icon
{{ image(name="image20.png", alt="screenshot") }} on
![](image20.png) on
the left-hand side to bring up a fill window for the selected
securities.  
@@ -132,14 +132,14 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- This is accomplished by highlighting 1 or many securities and then
clicking on the time and sales icon
{{ image(name="image49.png", alt="screenshot") }} on
![](image49.png) on
the left-hand side
- Launching the synthetic window to create a synthetic product
- This is accomplished by highlighting either 1 or 2 securities and
clicking on the Synthetic icon
{{ image(name="image34.png", alt="screenshot") }} on
![](image34.png) on
the lower left-hand side
- Please note - If only 1 security is chosen, a create synthetic
@@ -148,7 +148,9 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
## Time and Sales
{{ youtube(id="nEkSGacjt0s") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/nEkSGacjt0s" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- Provides a display of real-time fills as they happen for 1 or many
securities
@@ -159,9 +161,9 @@ page_description = "The BTS Spark Market module contains the Market View and Tim
- On launching a security browser will appear, allowing you to choose
the products you would like to view.
{{ image(name="time_and_sales.png", alt="screenshot") }}
![](time_and_sales.png)
- From time and sales, you can launch the same items as the market
view with the exception of a launching the synthetic window to
create a synthetic product
- From the settings icon {{ image(name="settings_icon.png", alt="screenshot") }} you can customize the time and sales modifying grid; the colors of the aggressor, status of trades coming in, as well as font sizes and which columns to see.
- From the settings icon ![](settings_icon.png) you can customize the time and sales modifying grid; the colors of the aggressor, status of trades coming in, as well as font sizes and which columns to see.
@@ -3,13 +3,13 @@ title = "My Windows"
weight = 7
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Name and load windows with the My Windows module in BTS Spark"
+++
{{ youtube(id="pjm1JJ9Q3jM") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjm1JJ9Q3jM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
This is the housing of the windows that you have manually named and
creates a place where you can quickly and easily launch windows
@@ -20,18 +20,18 @@ click on any tab and the “Add to My Windows” menu will appear.  If you
click on Add to My Windows, it will give you the ability to type in a
text box on the tab itself.
{{ image(name="image16.png", alt="screenshot") }}
![](image16.png)
Once you have named the tab, click enter to get out of the editing view
of the text box.  Once that is done, it will display that name for that
tab moving forward, which will be displayed within the My Windows
section of the module launcher.
{{ image(name="image15.png", alt="screenshot") }}
![](image15.png)
Right-clicking on a tab that has already had its default title modified
will show the following which will allow you to rename the modified
title or remove it from the My Windows, which will then restore the
default standard title.
{{ image(name="image37.png", alt="screenshot") }}
![](image37.png)
@@ -3,13 +3,12 @@ title = "Settings"
weight = 8
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "The BTS Spark Settings module and workspaces management"
+++
{{ youtube(id="yGQfr_puT3w") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGQfr_puT3w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
## Workspaces
@@ -17,7 +16,7 @@ This is where your workspaces can be saved or switched
to pre-saved ones that you have created.  If you click on the
Settings module, the Workspaces
{{ image(name="image9.png", alt="screenshot") }}
![](image9.png)
Once the Workspace menu is launched you can save your current workspace
by typing in a name in the text box and clicking save.  This will save
@@ -27,4 +26,4 @@ switching to it from the workspace launcher later on.
You can also delete workspaces by clicking on the “X” to the right of
the saved workspace from the workspace menu.
{{ image(name="image51.png", alt="screenshot") }}
![](image51.png)
+42 -26
View File
@@ -3,33 +3,47 @@ title = "Spreader"
weight = 6
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "How to create and edit synthetic spreads in BTS Spark's spreader"
+++
{{ youtube(id="RZU9Wj0cETo") }}
{{ youtube(id="XEZpuPAgMC0") }}
{{ youtube(id="EVFK2yNQFS4") }}
{{ youtube(id="d5ywlHen6yo") }}
{{ youtube(id="Mmw8PNJA0Dw") }}
{{ youtube(id="QK-bBSH6Rc0") }}
- Launching the spreader from the launcher will bring up the
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/XEZpuPAgMC0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/EVFK2yNQFS4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/d5ywlHen6yo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Mmw8PNJA0Dw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/QK-bBSH6Rc0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- Launching the spreader from the module launcher will bring up the
synthetic management browser which appears like below:
{{ image(name="image35.png", alt="screenshot") }}
![](image35.png)
- From this window, it will show any synthetics that you have already
created as well as various other options of both managing the
synthetics and other tasks all from the left-hand side pane within
the window, including:
- Adding a new synthetic {{ image(name="image8.png", alt="screenshot") }}
- Removing a synthetic {{ image(name="image13.png", alt="screenshot") }}
- Changing a synthetic {{ image(name="image55.png", alt="screenshot") }}
- Cloning a synthetic {{ image(name="image28.png", alt="screenshot") }}
- Launching a ladder of a highlighted synthetic {{ image(name="image7.png", alt="screenshot") }}
- Launching an order book of selected synthetic {{ image(name="image43.png", alt="screenshot") }}
- Adding a new synthetic ![](image8.png)
- Removing a synthetic ![](image13.png)
- Changing a synthetic ![](image55.png)
- Cloning a synthetic ![](image28.png)
- Launching a ladder of a highlighted synthetic ![](image7.png)
- Launching an order book of selected synthetic ![](image43.png)
@@ -49,7 +63,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
- An empty synthetic browser will look like below without any legs
chosen:
{{ image(name="image31.png", alt="screenshot") }}
![](image31.png)
- Clicking on Add Leg on the top right of the window will launch the
security browser, which will allow you to choose the first leg
@@ -63,13 +77,13 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
name your synthetic product.  By default, it will auto-populate
based on the symbols you have chosen:
{{ image(name="image29.png", alt="screenshot") }}
![](image29.png)
- You can click into the text box to the right of the name and
customize the name to be whatever you would like, we’ll call this
ES\_CAL:
{{ image(name="image19.png", alt="screenshot") }}
![](image19.png)
- Also included on this row are what ticks you would like this to be
priced in as well as the format of the pricing
@@ -78,7 +92,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
as the option to add additional legs through the add leg box to the
right of the symbols:
{{ image(name="image18.png", alt="screenshot") }}
![](image18.png)
- The following rows are all part of the main parameters which can
all be modified according to the traders style of designing the
@@ -90,14 +104,14 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
details of how that parameter functions.  Below is an example of the
throttle details displayed:
{{ image(name="image56.png", alt="screenshot") }}
![](image56.png)
- Main parameters - these are the top 4 rows in the Create Synthetic
window which all synthetics will be required to have set.
 Everything below these can be customized to a trader's needs, however, the top 4 are the main foundational aspects used for the calculation of
the synthetic:
{{ image(name="image10.png", alt="screenshot") }}
![](image10.png)
- Quantity - This is the number of contracts of the security you
would like to be included in the calculation of the synthetic as
@@ -125,7 +139,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
displayed and click on such will display further details on how this
parameter is used:
{{ image(name="image72.png", alt="screenshot") }}
![](image72.png)
- Quote
@@ -134,7 +148,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
by defining a region on either side of the working quote order,
inside which quote updates will not be sent:
{{ image(name="image6.png", alt="screenshot") }}
![](image6.png)
- Pre-Hedge
@@ -147,7 +161,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
more than the threshold defined.  The hedge order will pay up by the
“Payup Ticks” as defined per leg.
{{ image(name="image54.png", alt="screenshot") }}
![](image54.png)
- Post-Hedge
@@ -158,7 +172,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
opposite inside market, it is re-priced to cross the opposite inside
market as defined by “cross ticks”
{{ image(name="image70.png", alt="screenshot") }}
![](image70.png)
- Time Based Cross - Basically the same as Ticks Away Cross,
however, this is based on time (in ms) on when to automatically cross
@@ -167,7 +181,7 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
away from the opposite inside market, it is re-priced to cross the
opposite inside market as defined by “cross ticks”
{{ image(name="image40.png", alt="screenshot") }}
![](image40.png)
- Size Based Cross - similar to ticks and time based cross, but based
on size instead.  If you define a threshold of the opposite inside
@@ -175,3 +189,5 @@ page_description = "How to create and edit synthetic spreads in BTS Spark's spre
the market.  If a legged hedge order’s price gets X amount of
“ticks” away from the opposite inside market, it is re-priced to
cross the opposite inside market as defined by “cross ticks”
![](image76.png)
@@ -4,10 +4,6 @@ weight = 1
sort_by = "weight"
insert_anchor_links = "right"
aliases = ["./bts_spark/Algos/"]
[extra]
page_description = "Definitions of the Algos built into BTS Spark and their parameters"
+++
## Lean
+62 -53
View File
@@ -3,9 +3,6 @@ title = "Trading"
weight = 5
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "BTS Spark's Trading module, including The Order Book, Fills, P&amp;L, Ladder, Order Ticket, and Algo Studio"
+++
@@ -13,11 +10,13 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
Order Book, Fills, P&L, Ladder, and Order Ticket
- It appears like below:
{{ image( name = "image44.png", alt = "screenshot") }}
![](image44.png)
## Order Book
{{ youtube(id="ygMFkjBBGeU") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ygMFkjBBGeU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- This provides a list of all orders a user has placed, by default it
will display only working orders
@@ -27,7 +26,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
other windows within BTS
- Appears like the following:
{{ lightbox( name = "image5.png", alt = "screenshot") }}
![](image5.png)
- You can sort all columns by single left clicking on the column
header which will sort in either descending or ascending order, an
@@ -35,14 +34,14 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
 Below is a display of the security column sorting in descending
fashion.
{{ image( name = "image60.png", alt = "screenshot") }}
![](image60.png)
- Filtering can occur, by hovering over any column header which will
display a “Filter” box.  Clicking on this will then display the
available components which can be filtered, below is an example of
clicking on the securities column
{{ image( name = "image52.png", alt = "screenshot") }}
![](image52.png)
- If the column is displaying time base information, a special style
box will appear when clicking on the filtering box providing more
@@ -54,45 +53,49 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- Since Open
- A specific user-defined period of time
{{ image( name = "image25.png", alt = "screenshot") }}
![](image25.png)
- The left-hand box within an order book provides users with further
functionality to quickly perform various tasks with live orders as
well as launch other modules.
{{ image(name="spark_menu_order_book.png", alt="the spark menu in the order book", pull="right") }}
well as launch other modules.  It appears like the below:
- {{ icon(name="image48.png", alt="Fills icon") }} Fills -
![](spark_menu_order_book.png)
- ![](image48.png)Fills -
clicking on this will launch a fills window
- {{ icon(name="image57.png", alt = "modify icon") }} Modify -
- ![](image57.png) Modify -
clicking on this will launch an order ticket for a live order
highlighted in the Order Book.
- {{ icon(name="image73.png", alt = "cancel selected icon")}} Cancel
- ![](image73.png)Cancel
selected - clicking on this will cancel the selected orders you have
highlighted in the order book.  You can choose more than one by
holding hold the Ctrl key when clicking on each row of orders
- {{ icon(name="image74.png", alt = "cancel buys icon")}} Cancel
- ![](image74.png)Cancel
buys - clicking on this will cancel all the buy orders you have in
the order book
- {{ icon(name="image33.png", alt = "cancel all icon")}} Cancel
- ![](image33.png)Cancel
sells - clicking on this will cancel all the sell orders you have in
the order book
- {{ icon(name="image2.png", alt = "cancel all icon")}} Cancel
- ![](image2.png)Cancel
all - clicking on this will cancel all working orders you have the
order book
- {{ icon(name = "recreate_icon.png", alt = "screenshot") }} Recreate - clicking this will recreate already cancelled orders, sending them back to market with the same price and quantity as they were previously at before being cancelled.
- {{ image( name = "collapse_all_icon.png", alt = "screenshot") }} Collapse all - This will collapse all trees inside the order book to show only the top level of the order.
- {{ icon( name = "expand_all_icon.png", alt = "screenshot") }} Expand all - This will expand to show everything inside the order book.
- {{ icon( name = "settings_icon.png", alt = "screenshot") }} Settings - This launches the settings for the orderbook.
- The mini orderbook is integrated into the ladder
- Middle click (click the mouse wheel) on a working order to display the mini orderbook
- From here, you can change the price and quantity of any working order
{{ image( name = "mini_orderbook.png", alt = "screenshot") }}
- ![](recreate_icon.png) Recreate - clicking this will recreate already cancelled orders, sending them back to market with the same price and quantity as they were previously at before being cancelled.
- ![](collapse_all_icon.png) Collapse all - This will collapse all trees inside the order book to show only the top level of the order.
- ![](expand_all_icon.png) Expand all - This will expand to show everything inside the order book.
- ![](settings_icon.png) Settings - This launches the settings for the orderbook.
- Furthermore, there is a “floating” or “mini orderbook” feature as well
integrated within the ladder itself.
- To access this, you need to center click with your mouse wheel over
working orders on the ladder.
- From here, you can change the price and quantity of any working order
within the mini orderbook straight from the window
- This looks like below:
![](mini_orderbook.png)
## Fills
- This provides a list of all fills a user has had for a given time
period, by default it will open up with “since open” time frame
period, by default it will open up with “since open” timeframe
selected showing you fills on the day thus far
- You can create multiple fill windows, filtered however you
like
@@ -103,7 +106,9 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
## P&amp;L
{{ youtube(id="xj6cEQ6By34") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/xj6cEQ6By34" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- This provides a running ledger of a users account(s) profit and
loss, broken down per symbol
@@ -120,18 +125,20 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- This is accomplished on the top bar, which when clicked on, displays
the choices for calculation, this appears like below:
{{ image( name = "pnl_calculation.png", alt = "screenshot") }}
![](pnl_calculation.png)
- Below is an example P&L window showing a user’s multiple accounts and
various positions in each:
{{ image( name = "pnl_window.png", alt = "screenshot") }}
![](pnl_window.png)
## Ladder
{{ youtube(id="Q0qsyc1nvYo") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Q0qsyc1nvYo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- The ladder was designed to be the most logically produced ladder on
the market, engineered and designed by professional traders for
@@ -142,12 +149,12 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
market with a single click
- Below is an example ladder:
{{ image( name = "ladder.png", alt = "screenshot") }}
![](ladder.png)
- The ladder consists of 2 major parts, the Ladder itself and the
information and control section on the left-hand side
- The information section on the left contains the following: &nbsp; {{ image( name = "image1.png", alt = "screenshot") }}
- The information section on the left contains the following: &nbsp; ![](image1.png)
- Day - showing the current price of the security on the day
- High - showing the high price of the security on the day
- Low - showing the low price of the security on the day
@@ -160,7 +167,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- The control section right underneath the information section on the
left-hand side offers the following:
{{ image( name = "image62.png", alt = "screenshot") }}
![](image62.png)
- The account in which the orders will flow into when trading this
security
@@ -195,7 +202,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
the stop price, by clicking on the “&lt;” to bring up the stop price
option:
{{ image( name = "image61.png", alt = "screenshot") }}
![](image61.png)
- Iceberg
@@ -206,7 +213,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
quantity.  You will need to define what type of order that will
be used for the child order to be sent.
{{ image( name = "image47.png", alt = "screenshot") }}
![](image47.png)
- Time Box
@@ -226,9 +233,9 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- Below are images of the type of time frame that can be used within
the time box
{{ image( name = "image42.png", alt = "screenshot") }}
![](image42.png)
         
{{ image( name = "image50.png", alt = "screenshot") }}
![](image50.png)
- Lean
- This is a specialized algo order which allows the user to
@@ -237,14 +244,14 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- “Cross” means to take an order working on the bid and 'cross' by
buying into the asks.  This is often referred to as “paying up” as well
{{ image( name = "lean_algo_control.png", alt = "screenshot") }}
![](lean_algo_control.png)
- Join
- This is a specialized algo order which allows the user to enter an
order which will watch the opposite-inside market and join when the
market flips and has at least threshold size as defined.
{{ image( name = "join_algo_control.png", alt = "screenshot") }}
![](join_algo_control.png)
- Top
- This is a specialized algo order which allows the user to
@@ -252,14 +259,14 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- If the order is on the market (current bid price) and the quantity
on that bid price is below the top threshold defined the order will
cancel and do nothing else
{{ image( name = "top_algo_control.png", alt = "screenshot") }}
![](top_algo_control.png)
- Offset
- The goal of the offset order is to send your order X amount of ticks
from where you actually place it on the ladder. This is sometimes
known as a "fat finger" safety.
{{ image( name = "offset_algo_control.png", alt = "screenshot") }}
![](offset_algo_control.png)
- OCO
@@ -269,26 +276,26 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
of the fill. Composing this order with Cover simulates scalping
functionality.
{{ image( name = "oco_algo_control.png", alt = "screenshot") }}
![](oco_algo_control.png)
- Cover
- A Cover order’s purpose is to fire a hedge order (of any algo-type) in response to fills from it’s child order on the opposite side of the market N ticks away.
{{ image( name = "cover_algo_control.png", alt = "screenshot") }}
![](cover_algo_control.png)
- Trigger
- The Trigger order is exactly as it sounds, where it will trigger an order based on conditions defined.
- This order also has exit strategy built in where a user can define when the trigger order can auto-cancel based on time as well as price conditional.
{{ image( name = "trigger_algo_control.png", alt = "screenshot") }}
![](trigger_algo_control.png)
- The middle of the control section is where you can click on
predefined values to quickly populate the volume at which you wish
to enter the market.
{{ image( name = "image17.png", alt = "screenshot") }}
![](image17.png)
- You can manually type in a quantity here as well
- Furthermore, if you have a value in the volume bar, you can
@@ -298,7 +305,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- By having the auto clear box checked, it will bring the value back
to zero after placing an order at the size chosen.
{{ image( name = "image59.png", alt = "screenshot") }}
![](image59.png)
-  The bottom section of the control area of a ladder has 4 aspects
to it
@@ -310,7 +317,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- Cancel All, which will cancel all orders on the ladder
- Settings, which will launch the ladder settings window which
appears like below
{{ image( name = "ladder_settings.PNG", alt = "screenshot") }}
![](ladder_settings.png)
- Fonts and Colors - This is where you can customize the overall main
aspects of the ladder.
- Market Break Lines - These are where can control the viewing of what
@@ -332,7 +339,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- The other, main part of the ladder, is the ladder itself as
displayed below
{{ image( name = "ladder_main_part.png", alt = "screenshot") }}
![](ladder_main_part.png)
- There are 5 main columns
@@ -349,7 +356,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- To enter an order directly by clicking in the ladder, left click on either the bid or ask volume with a volume greater than 0 on the predefined value box on the left-hand section
- A badge will be displayed within the orders column of the value you have chosen and the quantity filled as displayed below
{{ image( name = "ladder_badges.png", alt = "screenshot") }}
![](ladder_badges.png)
- To change the price level you wish to work the order, you can right-click and hold on the badge to move it up or down the ladder for the
desired price
@@ -360,7 +367,9 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
## Order Ticket
{{ youtube(id="bdC5pZ6T35I") }}
<div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/bdC5pZ6T35I" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
- The order ticket was designed to provide another means other than
the ladder to enter orders into the market
@@ -369,7 +378,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
which security to populate
- The order ticket appears like below
{{ image( name = "image27.png", alt = "screenshot") }}
![](image27.png)
- On the top left, you have the price for the order as well as the
volume
@@ -407,7 +416,7 @@ page_description = "BTS Spark's Trading module, including The Order Book, Fills,
- This is all in place in a very user friendly GUI as shown below:
{{ image( name = "algo_studio.png", alt = "screenshot") }}
![](algo_studio.png)
- There are 3 main areas of the Algo Studio:
-3
View File
@@ -3,9 +3,6 @@ title = "BTS Spark"
weight = 1
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Documentation for Blue Trading Systems' BTS Spark"
+++
Click on the headings on the left to expand their contents. If you need specific information quickly use the Search button in the top right of this page.
+11 -25
View File
@@ -3,21 +3,7 @@ title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Release notes for BTS Spark"
+++
## 2.13.0
### User Visible Changes
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
- Added support for sounds for new orders
- Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away
- Enabled the ladder to remember the left panel scroll position across front end restarts
### Bug Fixes
- Fixed the local fills window so that it does not show the admin fills
- Removed the deleted accounts from the ladder accounts dropdown
- Fixed the orderbook price for iceberg of spreaders
## 2.12.0
### User Visible Changes
- Added support for ICE market data and order execution
@@ -32,11 +18,11 @@ page_description = "Release notes for BTS Spark"
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
### Internal Changes
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
- To facilitate support, allowed the bts user to view and edit algos from all other users
- To facilitate support, allowed the bts user to view and edit algos from all other users
- Improved system tests
- Improved log messages in UI to include dispatcher thread name
- Added support for Windows core dumps in the UI
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
@@ -156,7 +142,7 @@ page_description = "Release notes for BTS Spark"
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlaid on the volume column
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
@@ -173,11 +159,11 @@ page_description = "Release notes for BTS Spark"
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volume column
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
- Fixed a bug in the order book where the right side ladder would display the top level order's security rather then current row's
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
- Added ladder settings for the mini order book
- Added a safety check to guard against unintentional market crosses
@@ -193,7 +179,7 @@ page_description = "Release notes for BTS Spark"
### User Visible Changes
- Introduced multiple settings profiles for the ladder
- This allows multiple standard configuration for the ladder, optionally with security specific variations
- Made Cover order more independent from its hedging children:
- Made Cover order more independent from its hedging chidren:
- Cover continues to operate even if an hedging child is canceled
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
@@ -240,7 +226,7 @@ page_description = "Release notes for BTS Spark"
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for which the legs were not present yet
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
## 2.1.1
### User Visible Changes
@@ -252,7 +238,7 @@ page_description = "Release notes for BTS Spark"
- Added support for the GLBX Iceberg order type
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
- Allowed the LTQ and VAP values to be overlaid on the same ladder column
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
- Added day position information to the ladder
- Added PIQ to the mini order book
- Fixed a rare bug that could lead to server failure when requesting market data
@@ -333,7 +319,7 @@ page_description = "Release notes for BTS Spark"
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
## 1.5.7
### User Visible Changes
- New videos have been added to the manual describing the [Algo Studio](@/bts_spark/Trading/_index.md#algo-studio) and [Spreader](@/bts_spark/Spreader/_index.md)
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/_index.md#algo-studio) and [Spreader](./bts_spark/Spreader/_index.md)
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
@@ -409,7 +395,7 @@ page_description = "Release notes for BTS Spark"
- Fixed a bug in handling of built-in bts to user that could lead to failure to login as bts in old systems
- Improved market data subsystem to support security definition updates
- Fixed a startup race in market data that could lead to data corruption on startup
- Increased default order price precision to 8 digits as required by some CME products
- Increased default order price precision to 8 digis as required by some CME products
## 1.4.11
### New Features
@@ -477,7 +463,7 @@ page_description = "Release notes for BTS Spark"
### Ladder Usability Improvements
- Controls found in Left Pane of the ladder can now be hidden via the checkboxes found under Ladder Form -> Settings button -> Left Pane tab
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accommodate the entire form
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accomodate the entire form
- The Left Pane can now grown and shrunk horizontally via the splitter between the left pane and the main ladder area
### Performance Improvements
- Requoting and hedging latency has been reduced by up to 30%
@@ -3,9 +3,6 @@ title = "System Requirements"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
[extra]
page_description = "Recommended and minimum systems requirements for BTS Spark"
+++
+2 -7
View File
@@ -1,20 +1,15 @@
#/bin/bash
#python -m mkdocs build -d site
./run_zola.sh build
ssh bts@bluetradesys.com "rm -r /home/bts/bts_website/zola/content/docs"
scp -r ./content bts@bluetradesys.com:/home/bts/bts_website/zola/content/docs
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
ssh bts@bluetradesys.com << EOF
cd /home/bts/doc/
cd doc
sudo rm -r /var/www/documentation/*
sudo mv ./public/* /var/www/documentation/
cd /home/bts/bts_website/zola/
./fix_links.sh
./deploy.sh
EOF
rm -r public
+9 -18
View File
@@ -1,4 +1,4 @@
.page, .menu-open .page {
.page {
position: absolute;
width: calc(100% - #{$sidebar-width});
height: 100%;
@@ -7,24 +7,15 @@
background: #f6f6f6;
padding-bottom: 20px;
transition: 0.5s;
left: $sidebar-width;
h1:hover .zola-anchor, h2:hover .zola-anchor, h3:hover .zola-anchor, h4:hover .zola-anchor, h5:hover .zola-anchor, h6:hover .zola-anchor {
visibility: visible;
}
.zola-anchor {
color: #4183c4;
padding-right: .5em;
margin-left: -1.5em;
padding-left: 10px;
text-decoration: none;
font-weight: initial;
float: left;
visibility: hidden;
&:hover {
text-decoration: none;
visibility: visible;
text-decoration: underline;
}
}
@@ -164,13 +155,13 @@
}
}
// @include min-screen(600px) {
// .page {
// left: $sidebar-width;
// }
// }
@include min-screen(600px) {
.page {
left: $sidebar-width;
}
}
.menu-closed .page {
.page-without-menu {
width: 100%;
left: 0;
+19 -46
View File
@@ -123,9 +123,27 @@
.page__header {
height: 50px;
position: fixed;
.menu-icon {
height: 50px;
width: 50px;
font-size: 24px;
text-align: center;
float: left;
position: relative;
transition: background .5s;
cursor: pointer;
@include menu-icon();
&:hover {
span {
background-color: black;
}
}
}
.search-icon {
height: 50px;
width: 50px;
display: inline-block;
@@ -138,50 +156,5 @@
&:hover {
color: black
}
@include menu-icon();
&:hover {
span {
background-color: black;
}
}
}
.search-icon, .font-icon {
height: 40px;
width: 40px;
display: inline-block;
text-align: center;
line-height: 40px;
color: rgba(0, 0, 0, 0.5);
cursor: pointer;
font-size: 2rem;
&:hover {
color: #eee;
background-color:#666;
border-radius: .2ex;
}
}
.font-icon{
display: inline-block;
// padding:0.6ex 1ex;
margin-left:1rem;
background:#333;
color:#eee;
border-radius: .2ex;
&:hover {
background:#666;
}
}
}
.dark-mode .font-icon{
background:#aaa;
color:#333;
}
+2 -17
View File
@@ -1,4 +1,4 @@
.menu, .menu-open .menu {
.menu {
height: 100%;
position: absolute;
left: 0;
@@ -39,7 +39,7 @@
}
}
.menu-closed .menu {
.menu-hidden {
width: 0;
}
@@ -47,19 +47,4 @@
.menu {
width: 0;
}
.menu-close-icon {
display: block;
padding:1ex;
font-size: 1.5em;
float:right;
}
}
@include min-screen(600px) {
.menu-close-icon {
display: none;
}
}
+3 -98
View File
@@ -1,7 +1,6 @@
@charset "utf-8";
@import "normalize";
@import "colorbox";
* {
box-sizing: border-box;
@@ -22,10 +21,6 @@ body {
letter-spacing: 0.2px;
}
body.dark-mode .page{
background:#444;
color:#eee;
}
@mixin min-screen($min-width: $body-width) {
@media screen and (min-width: $min-width) {
@@ -45,21 +40,9 @@ $sidebar-width: 300px;
@import "content";
@import "header";
@media screen and (min-width: 1920px)
{
// img.scaled-image {
// transform: scale(0.666);
// transform-origin: top left;
// }
}
.colorbox-inline img {
max-width: 100%;
}
.icon-image {
display:inline;
max-height: 1.4em;
img {
transform: scale(0.666);
transform-origin: top left;
}
.back-button {
@@ -75,82 +58,4 @@ $sidebar-width: 300px;
iframe {
width:560px;
height:315px;
}
.youtube-thumb {
margin-top:2em;
position:relative;
max-width:500px;
}
.youtube-overlay {
position:absolute;
width:8rem;
height:4.5rem;
line-height:4.5rem;
font-size: 2.5rem;
left:calc(50% - 4rem);
top:calc(50% - 2.25rem);
text-align:center;
opacity:.8;
background:#f68a28;
color:white;
border-radius: .3rem;
}
.youtube-overlay:hover {
background:#f68a28;
opacity:1;
}
.video-parent {
position: relative;
width: fit-content;
}
.video-parent:hover .play-pause, .video-parent.paused .play-pause {
display: block;
}
.play-pause {
display:none;
opacity:.7;
position:absolute;
width:8rem;
height:4.5rem;
line-height:4.5rem;
font-size: 2.5rem;
left:calc(50% - 4rem);
top:calc(50% - 2.25rem);
text-align:center;
background:#f68a28;
color:#fff;
border-radius: .3rem;
z-index:1000;
}
.play-pause:hover {
opacity: 0.9;
}
@media (hover: none) {
.play-pause {
display:block;
}
.video-parent.playing:hover .play-pause {
display:none;
}
}
.is-clearfix {
clear:both;
}
.is-pulled-left {
float: left;
}
.is-pulled-right {
float: right;
}
-42
View File
@@ -1,42 +0,0 @@
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute;top:0;left:0;z-index:9999;overflow:hidden;-webkit-transform:translate3d(0,0,0);}
#cboxWrapper{max-width:none;}
#cboxOverlay{position:fixed;width:100%;height:100%;}
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
#cboxContent{position:relative;}
#cboxLoadedContent{overflow:auto;-webkit-overflow-scrolling:touch;}
#cboxTitle{margin:0;}
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute;top:0;left:0;width:100%;height:100%;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
.cboxPhoto{float:left;margin:auto;border:0;display:block;max-width:none;-ms-interpolation-mode:bicubic;}
.cboxIframe{width:100%;height:100%;display:block;border:0;padding:0;margin:0;}
#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;}
#cboxOverlay{background:#000;opacity:0.9;filter:alpha(opacity = 90);}
#colorbox{outline:0;}
#cboxTopLeft{width:14px;height:14px;background:url(colorbox-images/controls.png) no-repeat 0 0;}
#cboxTopCenter{height:14px;background:url(colorbox-images/border.png) repeat-x top left;}
#cboxTopRight{width:14px;height:14px;background:url(colorbox-images/controls.png) no-repeat -36px 0;}
#cboxBottomLeft{width:14px;height:43px;background:url(colorbox-images/controls.png) no-repeat 0 -32px;}
#cboxBottomCenter{height:43px;background:url(colorbox-images/border.png) repeat-x bottom left;}
#cboxBottomRight{width:14px;height:43px;background:url(colorbox-images/controls.png) no-repeat -36px -32px;}
#cboxMiddleLeft{width:14px;background:url(colorbox-images/controls.png) repeat-y -175px 0;}
#cboxMiddleRight{width:14px;background:url(colorbox-images/controls.png) repeat-y -211px 0;}
#cboxContent{background:#fff;overflow:visible;}
.cboxIframe{background:#fff;}
#cboxError{padding:50px;border:1px solid #ccc;}
#cboxLoadedContent{margin-bottom:5px;}
#cboxLoadingOverlay{background:url(colorbox-images/loading_background.png) no-repeat center center;}
#cboxLoadingGraphic{background:url(colorbox-images/loading.gif) no-repeat center center;}
#cboxTitle{position:absolute;bottom:-25px;left:0;text-align:center;width:100%;font-weight:bold;color:#7C7C7C;}
#cboxCurrent{position:absolute;bottom:-25px;left:58px;font-weight:bold;color:#7C7C7C;}
#cboxPrevious, #cboxNext, #cboxSlideshow, #cboxClose{border:0;padding:0;margin:0;overflow:visible;position:absolute;bottom:-29px;background:url(colorbox-images/controls.png) no-repeat 0px 0px;width:23px;height:23px;text-indent:-9999px;}
#cboxPrevious:active, #cboxNext:active, #cboxSlideshow:active, #cboxClose:active{outline:0;}
#cboxPrevious{left:0px;background-position:-51px -25px;}
#cboxPrevious:hover{background-position:-51px 0px;}
#cboxNext{left:27px;background-position:-75px -25px;}
#cboxNext:hover{background-position:-75px 0px;}
#cboxClose{right:0;background-position:-100px -25px;}
#cboxClose:hover{background-position:-100px 0px;}
.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px;right:27px;}
.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;}
.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px;right:27px;}
.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;}
+6 -15
View File
@@ -1,19 +1,10 @@
function initToggleMenu() {
$(".menu-icon").click(function (e) {
let body = $("body");
let mobile = window.innerWidth < 600;
let menu_closed = body.hasClass("menu-closed");
let menu_open = body.hasClass("menu-open");
let new_page = !(menu_closed || menu_open);
if((!mobile && (new_page || menu_open)) || mobile && menu_open) {
body.addClass("menu-closed");
body.removeClass("menu-open");
}
else {
body.removeClass("menu-closed");
body.addClass("menu-open");
}
var $menu = document.querySelector(".menu");
var $menuIcon = document.querySelector(".menu-icon");
var $page = document.querySelector(".page");
$menuIcon.addEventListener("click", function () {
$menu.classList.toggle("menu-hidden");
$page.classList.toggle("page-without-menu");
});
}
function debounce(func, wait) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

+4 -58
View File
@@ -1,40 +1,11 @@
$(document).ready(function() {
jQuery('a.thumbnail').colorbox({rel:'gal'});
jQuery('a.gallery_image').colorbox({rel:'gal'});
jQuery(".youtube").colorbox({iframe:true, innerWidth:'75%', innerHeight:'75%'});
jQuery(".font-icon").click(function (){
jQuery("body").toggleClass("dark-mode");
if(getCookie("dark-mode") === "true") {
setCookie("dark-mode", "false", 365);
}
else {
setCookie("dark-mode", "true", 365);
}
$("img").each(function(idx, elm){
$(elm).css("margin-bottom", elm.height * -0.333);
}).on("load", function(e){
$(e.target).css("margin-bottom", e.target.height * -0.333);
});
if(getCookie("dark-mode") === "true") {
jQuery("body").toggleClass("dark-mode");
}
if(window.innerWidth >= 1920) {
// $("img.scaled-image").each(function(idx, elm){
// $(elm).css("margin-bottom", elm.height * -0.333);
// }).on("load", function(e){
// $(e.target).css("margin-bottom", e.target.height * -0.333);
// });
$("a.colorbox-inline").each(function(idx, elm){
let child = $(elm).children("img")[0];
let w = (child.naturalWidth * 0.666) + 1
let h = (child.naturalHeight * 0.666) + 1
$(elm).colorbox({inline: true, innerWidth: w, innerHeight: h, scrolling: false});
});
}
else {
jQuery("a.colorbox-inline").colorbox({inline:true});
}
$(".back-button")
.html("Go Back")
.click(function(_) { window.history.back()});
@@ -43,29 +14,4 @@ $(document).ready(function() {
console.log(navigator.userAgent);
$(".search-icon").css("display", "none");
}
});
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
-6
View File
@@ -1,6 +0,0 @@
/*!
Colorbox 1.6.3
license: MIT
http://www.jacklmoore.com/colorbox
*/
(function(t,e,i){function n(i,n,o){var r=e.createElement(i);return n&&(r.id=Z+n),o&&(r.style.cssText=o),t(r)}function o(){return i.innerHeight?i.innerHeight:t(i).height()}function r(e,i){i!==Object(i)&&(i={}),this.cache={},this.el=e,this.value=function(e){var n;return void 0===this.cache[e]&&(n=t(this.el).attr("data-cbox-"+e),void 0!==n?this.cache[e]=n:void 0!==i[e]?this.cache[e]=i[e]:void 0!==X[e]&&(this.cache[e]=X[e])),this.cache[e]},this.get=function(e){var i=this.value(e);return t.isFunction(i)?i.call(this.el,this):i}}function h(t){var e=W.length,i=(A+t)%e;return 0>i?e+i:i}function a(t,e){return Math.round((/%/.test(t)?("x"===e?E.width():o())/100:1)*parseInt(t,10))}function s(t,e){return t.get("photo")||t.get("photoRegex").test(e)}function l(t,e){return t.get("retinaUrl")&&i.devicePixelRatio>1?e.replace(t.get("photoRegex"),t.get("retinaSuffix")):e}function d(t){"contains"in x[0]&&!x[0].contains(t.target)&&t.target!==v[0]&&(t.stopPropagation(),x.focus())}function c(t){c.str!==t&&(x.add(v).removeClass(c.str).addClass(t),c.str=t)}function g(e){A=0,e&&e!==!1&&"nofollow"!==e?(W=t("."+te).filter(function(){var i=t.data(this,Y),n=new r(this,i);return n.get("rel")===e}),A=W.index(_.el),-1===A&&(W=W.add(_.el),A=W.length-1)):W=t(_.el)}function u(i){t(e).trigger(i),ae.triggerHandler(i)}function f(i){var o;if(!G){if(o=t(i).data(Y),_=new r(i,o),g(_.get("rel")),!$){$=q=!0,c(_.get("className")),x.css({visibility:"hidden",display:"block",opacity:""}),I=n(se,"LoadedContent","width:0; height:0; overflow:hidden; visibility:hidden"),b.css({width:"",height:""}).append(I),j=T.height()+k.height()+b.outerHeight(!0)-b.height(),D=C.width()+H.width()+b.outerWidth(!0)-b.width(),N=I.outerHeight(!0),z=I.outerWidth(!0);var h=a(_.get("initialWidth"),"x"),s=a(_.get("initialHeight"),"y"),l=_.get("maxWidth"),f=_.get("maxHeight");_.w=Math.max((l!==!1?Math.min(h,a(l,"x")):h)-z-D,0),_.h=Math.max((f!==!1?Math.min(s,a(f,"y")):s)-N-j,0),I.css({width:"",height:_.h}),J.position(),u(ee),_.get("onOpen"),O.add(F).hide(),x.focus(),_.get("trapFocus")&&e.addEventListener&&(e.addEventListener("focus",d,!0),ae.one(re,function(){e.removeEventListener("focus",d,!0)})),_.get("returnFocus")&&ae.one(re,function(){t(_.el).focus()})}var p=parseFloat(_.get("opacity"));v.css({opacity:p===p?p:"",cursor:_.get("overlayClose")?"pointer":"",visibility:"visible"}).show(),_.get("closeButton")?B.html(_.get("close")).appendTo(b):B.appendTo("<div/>"),w()}}function p(){x||(V=!1,E=t(i),x=n(se).attr({id:Y,"class":t.support.opacity===!1?Z+"IE":"",role:"dialog",tabindex:"-1"}).hide(),v=n(se,"Overlay").hide(),L=t([n(se,"LoadingOverlay")[0],n(se,"LoadingGraphic")[0]]),y=n(se,"Wrapper"),b=n(se,"Content").append(F=n(se,"Title"),R=n(se,"Current"),P=t('<button type="button"/>').attr({id:Z+"Previous"}),K=t('<button type="button"/>').attr({id:Z+"Next"}),S=n("button","Slideshow"),L),B=t('<button type="button"/>').attr({id:Z+"Close"}),y.append(n(se).append(n(se,"TopLeft"),T=n(se,"TopCenter"),n(se,"TopRight")),n(se,!1,"clear:left").append(C=n(se,"MiddleLeft"),b,H=n(se,"MiddleRight")),n(se,!1,"clear:left").append(n(se,"BottomLeft"),k=n(se,"BottomCenter"),n(se,"BottomRight"))).find("div div").css({"float":"left"}),M=n(se,!1,"position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;"),O=K.add(P).add(R).add(S)),e.body&&!x.parent().length&&t(e.body).append(v,x.append(y,M))}function m(){function i(t){t.which>1||t.shiftKey||t.altKey||t.metaKey||t.ctrlKey||(t.preventDefault(),f(this))}return x?(V||(V=!0,K.click(function(){J.next()}),P.click(function(){J.prev()}),B.click(function(){J.close()}),v.click(function(){_.get("overlayClose")&&J.close()}),t(e).bind("keydown."+Z,function(t){var e=t.keyCode;$&&_.get("escKey")&&27===e&&(t.preventDefault(),J.close()),$&&_.get("arrowKey")&&W[1]&&!t.altKey&&(37===e?(t.preventDefault(),P.click()):39===e&&(t.preventDefault(),K.click()))}),t.isFunction(t.fn.on)?t(e).on("click."+Z,"."+te,i):t("."+te).live("click."+Z,i)),!0):!1}function w(){var e,o,r,h=J.prep,d=++le;if(q=!0,U=!1,u(he),u(ie),_.get("onLoad"),_.h=_.get("height")?a(_.get("height"),"y")-N-j:_.get("innerHeight")&&a(_.get("innerHeight"),"y"),_.w=_.get("width")?a(_.get("width"),"x")-z-D:_.get("innerWidth")&&a(_.get("innerWidth"),"x"),_.mw=_.w,_.mh=_.h,_.get("maxWidth")&&(_.mw=a(_.get("maxWidth"),"x")-z-D,_.mw=_.w&&_.w<_.mw?_.w:_.mw),_.get("maxHeight")&&(_.mh=a(_.get("maxHeight"),"y")-N-j,_.mh=_.h&&_.h<_.mh?_.h:_.mh),e=_.get("href"),Q=setTimeout(function(){L.show()},100),_.get("inline")){var c=t(e);r=t("<div>").hide().insertBefore(c),ae.one(he,function(){r.replaceWith(c)}),h(c)}else _.get("iframe")?h(" "):_.get("html")?h(_.get("html")):s(_,e)?(e=l(_,e),U=_.get("createImg"),t(U).addClass(Z+"Photo").bind("error."+Z,function(){h(n(se,"Error").html(_.get("imgError")))}).one("load",function(){d===le&&setTimeout(function(){var e;_.get("retinaImage")&&i.devicePixelRatio>1&&(U.height=U.height/i.devicePixelRatio,U.width=U.width/i.devicePixelRatio),_.get("scalePhotos")&&(o=function(){U.height-Line truncated
-131
View File
@@ -1,131 +0,0 @@
jQuery(function($)
{
// Default time delay before checking location
var call_back_delay = 500;
// Set some flags for tracking & execution
var scroll_timer = null;
var scroller = false; // indicate the user has begun scrolling
scroll_points_in_page = jQuery(".scroll_point");
video_screenshots = jQuery(".video-screenshot");
video_parents = jQuery(".video-parent");
var apply_video_click = function (video) {
$(video).click(function (e) {
var v = $(this)[0];
if(v.paused) {
v.play();
$(v).attr("data-paused", "false");
}
else {
v.pause();
$(v).attr("data-paused", "true");
}
});
}
for(var i = 0; i < video_parents.length; i++) {
let video_with_event = $(video_parents[i]).children("video")[0];
let play_icon = $($(video_parents[i]).children(".play-pause")[0]).children("i");
let parent = $(video_parents[i]);
parent.addClass("playing");
video_with_event.onpause = function () {
parent.addClass("paused");
parent.removeClass("playing");
play_icon.addClass("fa-play");
play_icon.removeClass("fa-pause");
}
video_with_event.onplay = function () {
parent.removeClass("paused");
parent.addClass("playing");
play_icon.removeClass("fa-play");
play_icon.addClass("fa-pause");
}
}
for(var i = 0; i < video_screenshots.length; i++) {
apply_video_click(video_screenshots[i]);
}
$(".play-pause").click(function (e) { $(this).next("video").click();});
// Check the location and track user
function track_location()
{
view_bottom = $(window).height() + $(window).scrollTop();// the bottom of the visible area
// loop through the scroll points and send events for any within the visible part of the page
for(var i = 0; i < scroll_points_in_page.length; i++)
{
check_scroll_point(scroll_points_in_page[i]);
}
for(var i = 0; i < video_screenshots.length; i++)
{
video_screenshots_play_pause(video_screenshots[i]);
}
}
var check_scroll_point = function(current_value)
{
var current_object = $(current_value);
if (view_bottom >= current_object.offset().top && $(window).scrollTop() <= current_object.offset().top && (current_object.attr("hit") === undefined || current_object.attr("hit") === null))
{
if (!debug_mode)
{
ga('send', 'event', current_object.attr("data-eventCategory") + "_scroll_points", "in_view", current_object.attr("data-eventLabel"));
}
else
{
console.log(current_object.attr("data-eventCategory") + "_scroll_points, " + current_object.attr("data-eventLabel"));
}
$("[data-eventLabel='" + current_object.attr("data-eventLabel")+ "']").attr("hit", "1"); // after the point has been hit don't count it again
}
}
var video_screenshots_play_pause = function(current_value)
{
var current_object = $(current_value);
var video_middle = current_object.offset().top + (current_object.height() / 2);
var video_on_screen = view_bottom > video_middle && video_middle > $(window).scrollTop();
if (
window.innerWidth > 600
&& video_on_screen
&& current_value.paused
&& current_object.attr("data-paused") === "false"
)
{
current_value.play();
}
else if (!current_value.paused && !video_on_screen) {
current_value.pause();
}
}
// Track the scrolling and track location
$(".page").scroll(function()
{
if (scroll_timer) {
clearTimeout(scroll_timer);
}
// Use a buffer so we don't call track_location too often.
scroll_timer = setTimeout(track_location, call_back_delay);
});
});
-1
View File
@@ -1 +0,0 @@
<a class="zola-anchor" href="#{{ id }}" aria-label="Anchor link for: {{ id }}"><i class="fas fa-link"></i></a>
+13 -23
View File
@@ -5,12 +5,9 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<meta name="keywords" content="Futures, Options, Derivatives, Trade, Trading, Professional, options trading, futures trading, Software" />
<meta name="rights" content="© Blue Trading Systems" />
<!-- <meta name="keywords" content="Futures, Derivatives, Trade, Trading, Trader, Professional, futures trading, Software" />
<meta name="description" content="Documentation for Blue Trading Systems' BTS Spark" /> -->
{% block meta %}
{% endblock meta %}
<meta name="description" content="Documentation for Blue Trading Systems' BTS Spark" />
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
@@ -39,8 +36,12 @@
}
</script>
<script src="https://kit.fontawesome.com/93aca32d82.js"></script>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script type="text/javascript" src="{{ get_url(path="image.js") | safe }}"></script>
{% block js %}
{% endblock js %}
@@ -59,9 +60,6 @@
{% block before_menu %}
{% endblock before_menu %}
<nav role="navigation">
<div class="menu-icon menu-close-icon">
<i class="fas fa-arrow-left"></i>
</div>
<ul>
{% block menu %}
{% set index = get_section(path="_index.md") %}
@@ -118,14 +116,14 @@
<div class="page">
<div class="page__header">
<span class="menu-icon">
<i class="fas fa-bars"></i>
</span>
<div class="menu-icon">
<span></span>
<span></span>
<span></span>
</div>
{% if config.build_search_index %}
<span class="search-icon"><i class="fas fa-search"></i></span>
<span class="search-icon">🔎</span>
{% endif %}
<span class="font-icon"><i class="fas fa-font"></i></span>
</div>
<div class="page__content">
@@ -156,19 +154,11 @@
</div>
{% block js_body %}
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script type="text/javascript" src="{{ get_url(path="jquery.colorbox-min.js") | safe }}"></script>
<script type="text/javascript" src="{{ get_url(path="image.js") | safe }}"></script>
<script type="text/javascript" src="{{ get_url(path="scroll_actions.js") | safe }}"></script>
{% if config.build_search_index %}
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") | safe }}"></script>
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") | safe }}"></script>
{% endif %}
<script type="text/javascript" src="{{ get_url(path="book.js") | safe }}"></script>
{% endblock js_body %}
</body>
-8
View File
@@ -1,13 +1,5 @@
{% extends "index.html" %}
{% block meta %}
{% if page.extra.page_description %}
<meta name="description" content="{{ page.extra.page_description }}" />
{% else %}
<meta name="description" content="{{ page.title }} Documentation for Blue Trading Systems' BTS Spark" />
{% endif %}
{% endblock meta %}
{% block title %}{{ page.title }} &bull; {{ config.title }}{% endblock title %}
{% block content %}
-8
View File
@@ -1,13 +1,5 @@
{% extends "index.html" %}
{% block meta %}
{% if section.extra.page_description %}
<meta name="description" content="{{ section.extra.page_description }}" />
{% else %}
<meta name="description" content="{{ section.title }} Documentation for Blue Trading Systems' BTS Spark" />
{% endif %}
{% endblock meta %}
{% block title %}{{ section.title }} &bull; {{ config.title }}{% endblock title %}
{% block content %}
-1
View File
@@ -1 +0,0 @@
<div style="clear:both;"></div>
-17
View File
@@ -1,17 +0,0 @@
{% if class %}
{% set cls = class %}
{% else %}
{% set cls = "" %}
{% endif %}
{% if page %}
{% set context = page %}
{% elif section %}
{% set context = section %}
{% endif %}
<img
class="icon-image {{cls}}"
src="{{name}}"
alt="{{alt}}"
/>
-34
View File
@@ -1,34 +0,0 @@
{% if pull %}
{% if pull == "left" %}
{% set float = "is-pulled-left" %}
{% elif pull == "right" %}
{% set float = "is-pulled-right" %}
{% else %}
{% set float = "is-pulled-over-there" %}
{% endif %}
{% else %}
{% set float = " " %}
{% endif %}
{% if class %}
{% set cls = class %}
{% else %}
{% set cls = "" %}
{% endif %}
{% if page %}
{% set context = page %}
{% elif section %}
{% set context = section %}
{% endif %}
{% set image_path = context.path ~ name %}
{% set meta = get_image_metadata(path = image_path) %}
{% set image_width = (meta.width * 0.666)%}
<img
class="{{cls}} {{float}}"
style="width:{{image_width}}px;"
src="{{name}}"
alt="{{alt}}"
/>
@@ -1,52 +0,0 @@
{% if pull %}
{% if pull == "left" %}
{% set float = "is-pulled-left" %}
{% elif pull == "right" %}
{% set float = "is-pulled-right" %}
{% else %}
{% set float = "is-pulled-over-there" %}
{% endif %}
{% else %}
{% set float = " " %}
{% endif %}
{% if class %}
{% set cls = class %}
{% else %}
{% set cls = "" %}
{% endif %}
{% if width %}
{% set wdth = width %}
{% else %}
{% set wdth = "100%" %}
{% endif %}
{% if page %}
{% set context = page %}
{% elif section %}
{% set context = section %}
{% endif %}
{% set image_path = context.path ~ name %}
{% set meta = get_image_metadata(path = image_path) %}
{% set image_width = (meta.width * 0.666)%}
{% set id = name | replace(from=".", to="-") %}
<a class="colorbox-inline" href="#{{id}}">
<img
class="{{cls}} {{float}}"
style="width: {{wdth}}; max-width:{{image_width}}"
src="{{name}}"
alt="{{alt}}"
/>
</a>
<div id="colorbox-inline-content" style="display: none;">
<div id="{{id}}">
<img class = "" style="width:{{image_width}}; max-width:95vw; max-height:95vh; object-fit: scale-down"
src="{{name}}"
alt="{{alt}}"
/>
</div>
</div>
@@ -1,6 +0,0 @@
<div class="video-parent">
<div class="play-pause"><i class="fas fa-play"></i></div>
<video class="video-screenshot" style="width: {{width}}; max-width:100%;" muted loop data-paused="false">
<source src="{{name}}" type="video/mp4">
</video>
</div>
@@ -1,8 +0,0 @@
<div {% if class %}class="{{class}}"{% endif %}>
<div class="embed-responsive embed-responsive-16by9 youtube-thumb">
<a class="gallery_image youtube cboxElement" href="https://www.youtube.com/embed/{{id}}" style="display:block;">
<img class="video_thumb" style="width:100%;" src="https://img.youtube.com/vi/{{id}}/0.jpg" alt="Video Thumbnail" />
<div class="youtube-overlay"><i class="fas fa-play"></i></div>
</a>
</div>
</div>
+1 -1
View File
@@ -25,7 +25,7 @@ namespace mbts.spark {
usr.UserData.AccountPermissions.AccountPermissions
.Where(kv => (kv.Value.TradingPermission != null && kv.Value.TradingPermission.HasPermissions) || kv.Value.AdminPermission.HasPermissions)
.Select(kv => ad.Accounts.Where(a => a.Id == kv.Key).TryGetSingle(out var acc) ? acc : (BTSAccountImmutable?)null)
.Where(acc => acc.HasValue && acc.Value.IsPresent)
.Where(acc => acc.HasValue)
.Select(acc => acc.Value)
.ToDictionary(acc => acc.Id, acc => acc.Name) :
new Dictionary<uint, string>(0))
+2
View File
@@ -19,6 +19,7 @@ namespace mbts.ui {
$"{nameof(Smashicons)}.{name}.GeometryKey";
}
public const string
AddBoxKey = nameof(AddBoxKey),
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
@@ -125,6 +126,7 @@ namespace mbts.ui {
public static ImageSource Accounting { get { return load_resource_and_freeze("Accounting"); } }
public static ImageSource Add { get { return load_resource_and_freeze("Add"); } }
public static ImageSource AddBox => load_resource_and_freeze(AddBoxKey);
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
+41
View File
@@ -13,6 +13,47 @@
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
</sys:String>
<DrawingImage x:Key="{x:Static mbts:Icons.AddBoxKey}">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#E0E0E0">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0" IsClosed="True" IsFilled="True">
<PolyLineSegment>
<PolyLineSegment.Points>
<Point>100,0</Point>
<Point>100,100</Point>
<Point>0,100</Point>
</PolyLineSegment.Points>
</PolyLineSegment>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="4"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="25,50">
<LineSegment Point="75,50"/>
</PathFigure>
<PathFigure StartPoint="50,25">
<LineSegment Point="50,75"/>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/gregor-cresnar
Ttile: Add free icon
@@ -116,7 +116,7 @@ namespace mbts.spark {
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
grid_settings_manager = new GridSettingsManager(columns, col_settings_flags);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
//
@@ -159,14 +159,18 @@ namespace mbts.spark {
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
}),
WorkLoop.Start("MarketView", _refresh)
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -7,7 +7,6 @@ using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -79,7 +78,7 @@ namespace mbts.spark {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
comp_builder.Add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
@@ -146,16 +145,17 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
)
);
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
@@ -25,7 +25,6 @@ namespace mbts.spark {
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
@@ -52,23 +51,26 @@ namespace mbts.spark {
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager = new GridSettingsManager(columns);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in row_vms)
r.raise_invalidated();
}));
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -7,7 +7,6 @@ using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -70,16 +69,17 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
)
);
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => {
var gs_vm = _vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
@@ -81,9 +81,9 @@ namespace bts.shell {
#region QuoteParams
public static QuoteParams QuoteParams => new QuoteParams {
LeanQuantityRatio = 1,
//QuoteInsideMarketDistance = QuoteInsideMarketDistance,
QuoteInsideMarketDistance = QuoteInsideMarketDistance,
QuoteMaxOrderMove = QuoteMaxOrderMove,
//QuoteMuteZone = QuoteMuteZone,
QuoteMuteZone = QuoteMuteZone,
QuoteRepriceThrottle = QuoteRepriceThrottle,
QuoteInsideMarketDistanceWithTravel = QuoteInsideMarketDistanceWithTravel,
};
@@ -113,7 +113,6 @@ namespace bts.shell {
};
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
#endregion
#region PreHedge
@@ -1,33 +0,0 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceEnabledModel : RowModelBase<bool> {
readonly QuoteInsideMarketDistanceTowardModel _toward;
public QuoteInsideMarketDistanceEnabledModel(QuoteInsideMarketDistanceTowardModel toward) =>
_toward = toward;
public override string display_name { get; } = string.Empty;
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle == null) {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceThrottle;
_toward.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = null;
}
_toward.set_enabled(leg_tup, value);
}
}
}
}
@@ -1,39 +0,0 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceTowardModel : RowModelBase<uint> {
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public override string display_name { get; } = "Toward Distance";
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup).OnTowardDistance = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance ?
inside_market_distance.OnTowardDistance :
_value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
if (_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance)
inside_market_distance.OnTowardDistance = value;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup) != null;
QuoteInsideMarketDistanceThrottle _get_inside_market_distance(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey);
}
}
}
@@ -115,11 +115,6 @@ namespace mbts.spark {
"dictates if this logic applies to the quoting market or to the synthetic spread " +
"market");
public static ParameterSection InsideMarketDistance =>
new ParameterSection("Inside Market Distance", true,
"Reduces re-quoting by throttling quotes that are moving toward the inside market " +
"but are more than 'Toward Distance' away from the inside market");
public static ParameterSection InsideMarketThrottleThresholds =>
new ParameterSection("Inside Market Throttle Thresholds", true,
"Reduces re-quoting for quotes that are moving toward the inside market. " +
@@ -190,8 +190,6 @@ namespace mbts.spark {
var quote_inside_market_throttle_ticks = new QuoteInsideMarketThrottleTicksModel();
var quote_inside_market_throttle_min_move = new QuoteInsideMarketThrottleMinMoveModel();
var quote_inside_market_distance_toward = new QuoteInsideMarketDistanceTowardModel();
var pre_hedge_payless_threshold = new PreHedgePaylessThresholdModel();
var pre_hedge_payless_ticks = new PreHedgePaylessTicksModel();
@@ -229,8 +227,6 @@ namespace mbts.spark {
//quote_inside_market_throttle_ticks,
//quote_inside_market_throttle_min_move,
//new MaxTicksInsideMarketModel(),
new QuoteInsideMarketDistanceEnabledModel(quote_inside_market_distance_toward),
quote_inside_market_distance_toward,
new PreHedgePaylessEnabledModel(pre_hedge_payless_threshold, pre_hedge_payless_ticks),
pre_hedge_payless_threshold,
pre_hedge_payless_ticks,
@@ -153,8 +153,13 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox
x:Name="_tree_view"
Grid.RowSpan="2"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@@ -173,7 +178,7 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" VerticalScrollBarVisibility="Auto" Margin="8,4,8,0">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -429,19 +434,25 @@
Margin="0,6,0,0"
Padding="12,8"
/>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<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 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 selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</StackPanel>
</Grid>
</ScrollViewer>
<c:DialogSkirt
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
BorderThickness="0"
>
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
Save Algo
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
Revert Algo
</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -68,23 +69,12 @@ namespace mbts.spark {
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// add definition
_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.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));
}
// save all
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.DocumentCopy, _vm.clone_selected_definition_cmd));
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
show_in_toolbar = _ui_configuration.can_save_specific
});
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
@@ -117,30 +107,11 @@ namespace mbts.spark {
_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 AlgoDefinitionVM d) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
prompt = null;
icon = default;
}
if (prompt != null) {
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
switch (resp) {
case BTSMessageBoxButtons.Yes: break;
case BTSMessageBoxButtons.No:
default: return;
}
}
_vm.remove_definition(d).ignore();
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
vm => vm != null,
out var remove_cmd_observer)));
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
}
void _dispose_core() {
@@ -149,4 +120,4 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
}
}
}
}
@@ -38,10 +38,10 @@ namespace mbts.spark {
IWindowSettingsManager<GridSettings> _settings_manager;
int _total_row_count;
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, settings, _get_observable(bus, model, settings), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
@@ -132,7 +132,10 @@ namespace mbts.spark {
// rows
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
var col_flags = EnumHelper.get_values<Columns>().ToDictionary(
c => c.ToString(),
c => GridColumnSettingsFeatureFlags.ImmutableBackgroundColor);
grid_settings_manager = new GridSettingsManager(columns, col_flags);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
@@ -145,6 +148,19 @@ namespace mbts.spark {
_unfiltered_fills_changed += o.OnNext;
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
});
if (settings.grid != null && _settings_manager != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = settings.grid;
}
_rows_manager.on_settings(settings.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
},
settings.sorting_criterion);
// resources/subscriptions
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
@@ -154,22 +170,12 @@ namespace mbts.spark {
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
_unfiltered_fills_changed?.Invoke(t);
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
var fs = s.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
};
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
grid_settings_manager.load_from(s);
foreach (var r in rows)
r.raise_invalidated();
})
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -192,7 +198,7 @@ namespace mbts.spark {
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
public int total_row_count => _total_row_count;
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettingsManager grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
@@ -282,9 +288,12 @@ namespace mbts.spark {
account = fill_tuple.account;
}
public event Action invalidated;
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
//public OrderType root_algo_type => _or
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
@@ -303,14 +312,14 @@ namespace mbts.spark {
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
public string root_algo_display_name => fill_tuple.root_order_security?.display_name() ?? OrderType.None.display_name();
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
public void raise_invalidated() =>
invalidated?.Invoke();
}
}
}
}
@@ -26,7 +26,6 @@ namespace mbts.spark {
FillsView _view;
FillsSettings _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
@@ -36,8 +35,6 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsSettings> _state_changed;
public override string window_type => window_type_name;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
@@ -54,38 +51,29 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => {
var gs_vm = _vm.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
)
);
gs_vm.load(s.with(_vm.columns.Select(c => c.create())));
return gs_vm;
});
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = Disposable.Create(() =>
_state_changed?.Invoke(
_state = new FillsSettings(_state.orders,
_vm.get_sorting_criterion().ToList(),
_vm.get_filters(),
_vm.grid_settings_manager.create()
)
)
);
}
public override void Dispose() {
@@ -23,10 +23,6 @@ namespace mbts.spark {
// TODO: somehow allow windows that are using the default settings to still have individual column widths.
[ProtoContract]
class LadderColumnSettings : IEquatable<LadderColumnSettings> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderColumnSettings).GetHashCode();
public static LadderColumnSettings Default => new LadderColumnSettings {
show = true,
width = 50,
@@ -78,7 +74,14 @@ namespace mbts.spark {
other.show == show;
public override bool Equals(object obj) => Equals(obj as LadderColumnSettings);
public override int GetHashCode() => _type_hash;
public override int GetHashCode() =>
width.GetHashCode() +
background.GetHashCode() +
foreground.GetHashCode() +
font_family.GetHashCode() +
font_size.GetHashCode() +
font_weight.GetHashCode();
public static bool operator ==(LadderColumnSettings x, LadderColumnSettings y) => x.safe_equals(y);
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
@@ -86,9 +89,6 @@ namespace mbts.spark {
[ProtoContract]
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(HistogramBarAnimation).GetHashCode();
[ProtoMember(1)]
public ColorShowProto bars { get; set; }
[ProtoMember(2)]
@@ -102,14 +102,14 @@ namespace mbts.spark {
show_numbers == other.show_numbers;
public override bool Equals(object obj) => Equals(obj as HistogramBarAnimation);
public override int GetHashCode() => _type_hash;
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
bars.GetHashCode(),
show_numbers.GetHashCode());
}
[ProtoContract]
class VolumeColumnSettings : IEquatable<VolumeColumnSettings> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(VolumeColumnSettings).GetHashCode();
public static VolumeColumnSettings Default {
get {
var gen = LadderColumnSettings.Default;
@@ -149,14 +149,19 @@ namespace mbts.spark {
show_ltq.Equals(other.show_ltq);
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
public override int GetHashCode() => _type_hash;
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
general.GetHashCode(),
buy.GetHashCode(),
sell.GetHashCode(),
passive.GetHashCode(),
total.GetHashCode(),
show_ltq.GetHashCode());
}
[ProtoContract]
class LadderMarketBreakLine : IEquatable<LadderMarketBreakLine> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderMarketBreakLine).GetHashCode();
[ProtoMember(1)]
public bool show { get; set; }
[ProtoMember(2)]
@@ -179,7 +184,8 @@ namespace mbts.spark {
Equals(obj as LadderMarketBreakLine);
public override int GetHashCode() =>
_type_hash;
show.GetHashCode() +
(int)color;
}
[ProtoContract]
@@ -249,11 +255,6 @@ namespace mbts.spark {
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
public static readonly IEqualityComparer<LadderSettings> profile_equality_comparer =
EqualityComparer.Create<LadderSettings>(profile_equals);
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderSettings).GetHashCode();
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
@@ -496,9 +497,6 @@ namespace mbts.spark {
[ProtoMember(55)]
public LadderMiniOrderBookSettings mini_ob { get; set; }
[ProtoMember(57)]
public double left_panel_scroll_position { get; set; }
public void sanitize() =>
sanitize(this);
@@ -535,81 +533,112 @@ namespace mbts.spark {
return s;
}
/// <summary>
/// Determines whether or not the specified LadderSettings are 'profile equivalent.'
/// This means that 2 ladders, each with a different one of the specified settings, could share the same profile.
/// </summary>
///
/// Note: the impetus for this version of equality is to avoid disconnecting a ladder from its profile due to 'trivial' settings changes.
/// Consider the '<see cref="default_algo"/>' property:
/// If a ladder is using a settings profile with Limit as the default algo, and the user decides to send a 'Market' order,
/// the ladder would suddenly be disconnected from the profile, because it's settings state would no longer be equal to the profile's settings,
/// since they changed the selected algo type to 'Market.'
/// To avoid such travesties, a separate equals logic is needed: this is called 'profile-equivalent' and is implemented in <see cref="profile_equals)"/>.
/// Devs: be sure to account for properties that are excluded in the 'profile-equals' logic in the [regular]'equals' logic.
public static bool profile_equals(LadderSettings a, LadderSettings b) =>
a == null ? b == null :
b != null &&
a.row_height == b.row_height &&
a.grid_line_color == b.grid_line_color &&
a.orders_column.safe_equals(b.orders_column) &&
a.bid_column.safe_equals(b.bid_column) &&
a.ask_column.safe_equals(b.ask_column) &&
a.price_column.safe_equals(b.price_column) &&
a.last_column.safe_equals(b.last_column) &&
a.piq_column.safe_equals(b.piq_column) &&
a.calculator_buttons.safe_equals(b.calculator_buttons, Enumerable.SequenceEqual) &&
a.scroll_bar_visible == b.scroll_bar_visible &&
a.default_account == b.default_account &&
a.default_time_in_force == b.default_time_in_force &&
a.size == b.size &&
a.animate_recenter == b.animate_recenter &&
a.badge_style == b.badge_style &&
a.auto_clear == b.auto_clear &&
a.bid_break_line.safe_equals(b.bid_break_line) &&
a.ask_break_line.safe_equals(b.ask_break_line) &&
a.mid_break_line.safe_equals(b.mid_break_line) &&
a.alternate_order_size == b.alternate_order_size &&
a.session_low_line.safe_equals(b.session_low_line) &&
a.session_high_line.safe_equals(b.session_high_line) &&
a.session_range_fill.safe_equals(b.session_range_fill) &&
a.volume_column.safe_equals(b.volume_column) &&
a.size_display.safe_equals(b.size_display) &&
a.day_visible == b.day_visible &&
a.high_visible == b.high_visible &&
a.low_visible == b.low_visible &&
a.open_visible == b.open_visible &&
a.settle_visible == b.settle_visible &&
a.volume_visible == b.volume_visible &&
a.account_visible == b.account_visible &&
a.tif_visible == b.tif_visible &&
a.order_type_visible == b.order_type_visible &&
a.account_position_visible == b.account_position_visible &&
a.day_position_visible == b.day_position_visible &&
a.raw_day_position_visible == b.raw_day_position_visible &&
a.size_presets_visible == b.size_presets_visible &&
a.cancel_buys_visible == b.cancel_buys_visible &&
a.cancel_sells_visible == b.cancel_sells_visible &&
a.cancel_all_visible == b.cancel_all_visible &&
a.auto_clear_visible == b.auto_clear_visible &&
a.clear_visible == b.clear_visible &&
a.order_entry_panel_initial_width == b.order_entry_panel_initial_width &&
a.zoom_slider_visible == b.zoom_slider_visible &&
a.zoom_factor == b.zoom_factor &&
a.use_defaults == b.use_defaults &&
a.favorite_algos == b.favorite_algos &&
a.net_change == b.net_change &&
a.show_warn_on_cross == b.show_warn_on_cross &&
a.mini_ob == b.mini_ob;
public bool Equals(LadderSettings other) =>
profile_equals(this, other) &&
other != null &&
row_height == other.row_height &&
grid_line_color == other.grid_line_color &&
orders_column.safe_equals(other.orders_column) &&
bid_column.safe_equals(other.bid_column) &&
ask_column.safe_equals(other.ask_column) &&
price_column.safe_equals(other.price_column) &&
last_column.safe_equals(other.last_column) &&
piq_column.safe_equals(other.piq_column) &&
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
scroll_bar_visible == other.scroll_bar_visible &&
default_account == other.default_account &&
default_algo == other.default_algo &&
left_panel_scroll_position == other.left_panel_scroll_position;
default_time_in_force == other.default_time_in_force &&
size == other.size &&
animate_recenter == other.animate_recenter &&
badge_style == other.badge_style &&
auto_clear == other.auto_clear &&
bid_break_line.safe_equals(other.bid_break_line) &&
ask_break_line.safe_equals(other.ask_break_line) &&
mid_break_line.safe_equals(other.mid_break_line) &&
alternate_order_size == other.alternate_order_size &&
session_low_line.safe_equals(other.session_low_line) &&
session_high_line.safe_equals(other.session_high_line) &&
session_range_fill.safe_equals(other.session_range_fill) &&
volume_column.safe_equals(other.volume_column) &&
size_display.safe_equals(other.size_display) &&
day_visible == other.day_visible &&
high_visible == other.high_visible &&
low_visible == other.low_visible &&
open_visible == other.open_visible &&
settle_visible == other.settle_visible &&
volume_visible == other.volume_visible &&
account_visible == other.account_visible &&
tif_visible == other.tif_visible &&
order_type_visible == other.order_type_visible &&
account_position_visible == other.account_position_visible &&
day_position_visible == other.day_position_visible &&
raw_day_position_visible == other.raw_day_position_visible &&
size_presets_visible == other.size_presets_visible &&
cancel_buys_visible == other.cancel_buys_visible &&
cancel_sells_visible == other.cancel_sells_visible &&
cancel_all_visible == other.cancel_all_visible &&
auto_clear_visible == other.auto_clear_visible &&
clear_visible == other.clear_visible &&
order_entry_panel_initial_width == other.order_entry_panel_initial_width &&
zoom_slider_visible == other.zoom_slider_visible &&
zoom_factor == other.zoom_factor &&
use_defaults == other.use_defaults &&
favorite_algos == other.favorite_algos &&
net_change == other.net_change &&
show_warn_on_cross == other.show_warn_on_cross &&
mini_ob == other.mini_ob;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
public override int GetHashCode() => _type_hash;
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
row_height.GetHashCode(),
grid_line_color.GetHashCode(),
EqualityComparer.safe_get_hash_code(orders_column),
EqualityComparer.safe_get_hash_code(bid_column),
EqualityComparer.safe_get_hash_code(ask_column),
EqualityComparer.safe_get_hash_code(price_column),
EqualityComparer.safe_get_hash_code(last_column),
EqualityComparer.safe_get_hash_code(piq_column),
EqualityComparer.safe_get_hash_code(calculator_buttons),
scroll_bar_visible.GetHashCode(),
default_time_in_force.GetHashCode(),
size.GetHashCode(),
animate_recenter.GetHashCode(),
badge_style.GetHashCode(),
EqualityComparer.safe_get_hash_code(bid_break_line),
EqualityComparer.safe_get_hash_code(ask_break_line),
EqualityComparer.safe_get_hash_code(mid_break_line),
alternate_order_size.GetHashCode(),
EqualityComparer.safe_get_hash_code(session_low_line),
EqualityComparer.safe_get_hash_code(session_high_line),
EqualityComparer.safe_get_hash_code(session_range_fill),
EqualityComparer.safe_get_hash_code(volume_column),
EqualityComparer.safe_get_hash_code(size_display),
day_visible.GetHashCode(),
high_visible.GetHashCode(),
low_visible.GetHashCode(),
open_visible.GetHashCode(),
settle_visible.GetHashCode(),
volume_visible.GetHashCode(),
account_visible.GetHashCode(),
tif_visible.GetHashCode(),
order_type_visible.GetHashCode(),
account_position_visible.GetHashCode(),
size_presets_visible.GetHashCode(),
cancel_buys_visible.GetHashCode(),
cancel_sells_visible.GetHashCode(),
cancel_all_visible.GetHashCode(),
auto_clear_visible.GetHashCode(),
clear_visible.GetHashCode(),
order_entry_panel_initial_width.GetHashCode(),
zoom_slider_visible.GetHashCode(),
zoom_factor.GetHashCode(),
use_defaults.GetHashCode(),
favorite_algos.GetHashCode(),
net_change.GetHashCode(),
show_warn_on_cross.GetHashCode());
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -81,9 +81,11 @@ namespace bts.shell {
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
// profile was deleted
id = 0;
} else if (!LadderSettings.profile_equals(s, 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;
} else {
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;
}
}
}
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
@@ -147,7 +149,7 @@ namespace bts.shell {
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!LadderSettings.profile_equals(curr, last_value)) {
} else if (!curr.Equals(last_value)) {
settings_id = 0;
last_value = curr;
}
@@ -161,7 +163,7 @@ namespace bts.shell {
// not on a profile, don't respond to default settings changes
return;
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
if (!LadderSettings.profile_equals(last_value, vm.get_save_state())) {
if (!last_value.Equals(vm.get_save_state())) {
settings_id = 0;
} else {
settings_subject.OnNext(last_value = profile.value);
@@ -209,7 +211,6 @@ namespace bts.shell {
_ => _,
vm.get_save_state(),
UserSettings.FileExtensions.LADDER,
LadderSettings.profile_equals,
settings_id);
dialog_vm.applied += apply;
// dialog
@@ -35,14 +35,7 @@ namespace mbts.spark {
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double
_position,
_day_position,
_raw_day_position,
_left_panel_extent_height,
_left_panel_view_port_height,
_left_panel_scroll_position,
_left_panel_scroll_position_target;
double _position, _day_position, _raw_day_position;
bool _show_account_position, _show_day_position, _show_raw_day_position;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
@@ -83,7 +76,6 @@ namespace mbts.spark {
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
_left_panel_scroll_position_target = init.left_panel_scroll_position;
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
_volume = backing(nameof(volume), 0ul);
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
@@ -341,33 +333,6 @@ namespace mbts.spark {
public bool show_cancel_sells => _show_cancel_sells.view;
public bool show_cancel_all => _show_cancel_all.view;
public bool show_zoom => _show_zoom.view;
// left panel scroll props
public double left_panel_view_port_height {
get => _left_panel_view_port_height;
set {
using (detect_property_changes(nameof(left_panel_scroll_position)))
_left_panel_view_port_height = value;
}
}
public double left_panel_scroll_position {
get {
if (!_left_panel_scroll_position_target.is_nan()) {
if (_left_panel_scroll_position_target < _max_left_panel_scroll_position) {
_left_panel_scroll_position = _left_panel_scroll_position_target;
_left_panel_scroll_position_target = double.NaN;
}
}
return Math.Min(_left_panel_scroll_position, _left_panel_extent_height);
}
set => Set(ref _left_panel_scroll_position, value);
}
public double left_panel_extent_height {
get => _left_panel_extent_height;
set {
using (detect_property_changes(nameof(left_panel_scroll_position)))
Set(ref _left_panel_extent_height, value);
}
}
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
public string position_display_text {
@@ -399,10 +364,6 @@ namespace mbts.spark {
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings { get; }
double _max_left_panel_scroll_position =>
Math.Max(0, _left_panel_extent_height - _left_panel_view_port_height);
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
@@ -433,7 +394,6 @@ namespace mbts.spark {
zoom_factor,
piq_column_width));
rv.auto_clear = auto_clear;
rv.left_panel_scroll_position = left_panel_scroll_position;
if (order_properties_vm != null) {
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
if (order_properties_vm.selected_account.HasValue)
@@ -301,14 +301,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer
x:Name="_left_panel_scroll"
Grid.Row="0"
Grid.RowSpan="2"
mbts:ScrollViewerBehavior.VerticalOffset="{Binding left_panel_scroll_position, Mode=TwoWay}"
mbts:ScrollViewerBehavior.EnableTwoWayVerticalOffsetBinding="True"
VerticalScrollBarVisibility="Hidden"
>
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
@@ -6,12 +6,10 @@ using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
@@ -87,7 +85,6 @@ namespace mbts.spark {
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
this.vm = vm;
this.ConnectDataContext<LadderVM>(lvm => lvm.bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => Resources[ResourceKeys.Securities] = sec_map));
vm.SubscribeDisposed(_connect_left_panel_scroll());
DataContext = vm;
DataContextChanged += datacontext_changed;
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
@@ -95,39 +92,7 @@ namespace mbts.spark {
this.PreviewMouseDown += LadderView_PreviewMouseDown;
}
IDisposable _connect_left_panel_scroll() {
// extent height
bool busy = false;
var extent_height_hook = _left_panel_scroll.ObserveProperty(ScrollViewer.ExtentHeightProperty, (s, e) => {
if (!busy) {
double scroll_pos = vm.left_panel_scroll_position;
busy = true;
Dispatcher.InvokeAsync(() => {
vm.left_panel_extent_height = _left_panel_scroll.ExtentHeight;
if (_left_panel_scroll.VerticalOffset != scroll_pos)
_left_panel_scroll.TryScrollToVerticalOffset(scroll_pos);
busy = false;
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
});
// view port height
_left_panel_scroll.Loaded += on_loaded;
var dispose = Disposable.CreateDisposeOnce(dispose_impl);
return Disposable.Create(dispose, vm.SubscribeDisposed(dispose));
// local methods
void on_loaded(object sender, RoutedEventArgs e) {
if (_left_panel_scroll.descendants_of_type<ScrollBar>().TryGetFirst(sb => sb.Orientation == Orientation.Vertical, out var vscroll)) {
vscroll.SetBinding(ScrollBar.ViewportSizeProperty, new Binding(nameof(LadderVM.left_panel_view_port_height)) {
Converter = ValueConverter.create<double, Size>(d => new Size(0, d), sz => sz.Height),
Mode = BindingMode.OneWayToSource
});
}
}
void dispose_impl() {
Disposable.dispose(ref extent_height_hook);
_left_panel_scroll.Loaded -= on_loaded;
}
}
// (Left|Right) CTRL + Wheel to zoom
// This is neat b/c the user doesn't even need to have the slider visible to zoom
@@ -29,6 +29,7 @@ using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.Windows.Input;
using System.Reactive.Disposables;
using mbts.ui.dpi;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
@@ -46,10 +47,10 @@ namespace mbts.spark {
public OrderBookSortingData(IEnumerable<ICVOrderBookColumn> column_sorting_hierarchy) {
var comp_builder = new ComparerBuilder<OrderBookRowVM>();
foreach (var col in column_sorting_hierarchy)
comp_builder.add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
comp_builder.Add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
if (!column_sorting_hierarchy.Any(col => col.key == Columns.OrderKey)) {
// the resultant comparer must to be 100% deterministic, as SortedList (the building block of the SortedTree) uses a BinarySearch to find/remove items and does NOT check for item-equality.
comp_builder.add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
comp_builder.Add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
}
comparer = comp_builder.to_comparer();
sorting_criterion = column_sorting_hierarchy.Select(col => new SortingCriteria(col.id, col.SortDirection.GetValueOrDefault() == ListSortDirection.Ascending)).ToArray();
@@ -582,7 +583,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
#region Explicit Column Defs
@@ -592,7 +593,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
@@ -601,7 +602,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
@@ -610,7 +611,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_side_brush(row), background_brush, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
@@ -676,7 +677,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), background_brush, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
}
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
@@ -720,14 +721,23 @@ namespace mbts.spark {
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
//
var grid = new Grid { VerticalAlignment = VerticalAlignment.Center };
Grid.SetColumn(security_view, 1);
var grid = new Grid {
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(toggle_button, 1);
Grid.SetColumn(security_view, 2);
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(10.0.RoundLayoutValue(), GridUnitType.Pixel) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(toggle_button);
grid.Children.Add(security_view);
//
return new Border { Child = grid, Margin = new Thickness(10, 0, 0, 0) };
var container = new Grid {
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
container.SetBinding(Control.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
container.Children.Add(grid);
return container;
}
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
@@ -108,7 +108,7 @@ namespace mbts.spark {
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.display_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); }
@@ -141,22 +141,24 @@ namespace mbts.spark {
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
const string title = "Order Book 'Mini' Fills Grid";
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
_fills_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => fills_vm.value.grid_settings_manager.create_vm(
s => {
var gs_vm = fills_vm.value.grid_settings_manager.create_vm();
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(fills_vm.columns.Select(c => c.create()))
),
gs_vm.load(s.with(fills_vm.columns.Select(c => c.create())));
return gs_vm;
},
show_save_as_defaults: false);
// TODO: bind to main window's Title
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, rows, core_columns);
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
@@ -245,7 +247,7 @@ namespace mbts.spark {
.Subscribe(is_visible => {
if (is_visible) {
var init = _cached_fills_settings.to_startup_data();
fills_vm.value = new FillsVM(bus, model, Observable.Return(init), model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
fills_vm.value = new FillsVM(bus, model, init, model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
} else if (fills_vm.value != null) {
_cached_fills_settings = _get_fills_settings(fills_vm.value);
fills_vm.value = null;
@@ -261,7 +263,8 @@ namespace mbts.spark {
}),
_columns);
if (settings_manager != null) {
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
concat_disposable(
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager.load_from));
}
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
Observable.Create<Func<FillTuple, bool>>(o => {
@@ -176,8 +176,10 @@ namespace mbts.spark {
return _vm.grid_settings_manager.create_vm(s);
}
);
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
gs_vm.SubscribeDisposed(Disposable.Create(
literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => dialog.Resources[ResourceKeys.Securities] = sec_map)));
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
@@ -1,15 +1,17 @@
using bts.api;
using bts.settings;
using bts.shell.orderbook;
using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.shell {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
readonly IBus _bus;
readonly INotificationService _notification_service;
readonly INotificationServiceDataCache _notification_service_data_cache;
@@ -26,9 +28,7 @@ namespace bts.shell {
}
public override Task<IShellWindow> load(OrderBookStartupData state) {
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
(us, s) => us.with(order_book_grid: s));
var settings_manager = _settings_hub.create_orderbook_settings_manager(state.grid_settings_wrapper());
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
return Task.FromResult<IShellWindow>(w);
}
@@ -37,5 +37,17 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
[Export(typeof(IOrderBookGridSettingsMigrationRule))]
class EnableBackgroundMigrationRule : IOrderBookGridSettingsMigrationRule {
public bool apply(OrderBookGridSettings settings, out OrderBookGridSettings new_settings) {
if (!settings.general.enable_background_color) {
new_settings = settings.with(general: settings.general.with(enable_background_color: true));
return true;
}
new_settings = default;
return false;
}
}
}
}
@@ -7,6 +7,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
@@ -42,8 +43,8 @@ namespace bts.shell.orderbook {
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
public OrderBookGridSettings with(GridSettings general = null, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations?.ToList() ?? this.predicate_animations) {
algo_tree_display_mode = algo_tree_display_mode
};
@@ -1,23 +1,27 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
namespace bts.shell.orderbook {
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
class OrderBookGridSettingsManager : IDisposable {
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
readonly IEnumerable<OrderBookRowVM> _rows;
readonly ObservableCollection<OrderRecordTuplePredicateAnimations> _active_predicate_animations =
new ObservableCollection<OrderRecordTuplePredicateAnimations>();
IReadOnlyList<OrderRecordTuplePredicateAnimations> _predicate_animations;
AlgoTreeDisplayMode _algo_tree_display_mode;
IDisposable _sound_hooks;
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
@@ -27,7 +31,7 @@ namespace bts.shell.orderbook {
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontColor;
}
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
hard_column_flags = col_settings_flags;
@@ -36,36 +40,22 @@ namespace bts.shell.orderbook {
public OrderBookGridSettingsManager(
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
return pvm;
},
col_id => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
return pvm;
},
(gen, anims) => new OrderBookGridSettings(gen, anims.empty_if_null().Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
p => p.create_predicate(notification_service.data_cache),
p => p.create_notification_predicate(notification_service.data_cache),
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
hard_column_flags);
_core_man_as_observer = core_manager;
_rows = rows;
core_manager = new GridSettingsManager(columns, hard_column_flags);
_sound_hooks = ObservableAutoWrapper.connect_item_hooks(
_active_predicate_animations,
a => {
var pred = a.predicate_data.create_predicate(notification_service.data_cache);
return new CompositeDisposable(
a.sound_notifications.Select(s => notification_service.link_to_order(pred, s)));
});
}
public event Action algo_tree_display_mode_changed;
public GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_manager { get; }
public GridSettingsManager core_manager { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _algo_tree_display_mode;
@@ -78,23 +68,29 @@ namespace bts.shell.orderbook {
}
public void load_from(OrderBookGridSettings gs) {
core_manager.load_from(gs);
core_manager.load_from(gs.general);
algo_tree_display_mode = gs.algo_tree_display_mode;
_predicate_animations = gs.predicate_animations;
_active_predicate_animations.sync_with(_predicate_animations.Where(p => p.is_enabled));
foreach (var r in _rows)
r.raise_invalidated();
}
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) {
var gs = core_manager.create();
gs.general = gs.general.with(sorting_criterion: sorting_criterion);
gs.algo_tree_display_mode = _algo_tree_display_mode;
return gs;
}
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
algo_tree_display_mode = gs.algo_tree_display_mode
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) =>
new OrderBookGridSettings(
core_manager.create(),
_predicate_animations.ToList()) {
algo_tree_display_mode = _algo_tree_display_mode
};
public void Dispose() => core_manager.Dispose();
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) {
var vm = new OrderBookSettingsVM(core_manager.create_vm());
vm.load(gs);
return vm;
}
public void Dispose() =>
Disposable.dispose(ref _sound_hooks);
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
switch (col) {
@@ -122,12 +118,5 @@ namespace bts.shell.orderbook {
default: return default;
}
}
void IObserver<OrderBookGridSettings>.OnNext(OrderBookGridSettings value) {
_core_man_as_observer.OnNext(value);
algo_tree_display_mode = value.algo_tree_display_mode;
}
void IObserver<OrderBookGridSettings>.OnError(Exception error) => _core_man_as_observer.OnError(error);
void IObserver<OrderBookGridSettings>.OnCompleted() => _core_man_as_observer.OnCompleted();
}
}
@@ -1,46 +1,150 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookSettingsVM(GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm) =>
this.core_settings_vm = core_settings_vm;
public OrderBookSettingsVM(
IBus bus,
OrdersModel orders_model,
Func<OrderRecordTuplePredicate> predicate_data_factory,
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
Func<string, IPropertyWrapperVM<OrderRecordTuplePredicate>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
}
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode { get => _algo_tree_display_mode; set => Set(ref _algo_tree_display_mode, value); }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
gs.algo_tree_display_mode = algo_tree_display_mode;
return gs;
}
}
using bts.api;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.viewmodels;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
readonly BooleanParameterVM _show_full_algo_tree = new BooleanParameterVM("Show Full Algo Tree");
public OrderBookSettingsVM(GridSettingsVM core_settings_vm) {
this.core_settings_vm = core_settings_vm;
this.echo(_show_full_algo_tree, nameof(algo_tree_display_mode));
add_alert_cmd = new DelegateCommand(() => {
alerts.Add(new OrderAlertVM {
is_enabled = true,
predicate_type = OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
sound_type = SoundNotification.SystemSound.BellCounter
});
});
delete_alert_cmd = new DelegateCommand<OrderAlertVM>(vm => {
if (vm != null)
alerts.Remove(vm);
});
}
public GridSettingsVM core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _show_full_algo_tree.value ? AlgoTreeDisplayMode.FullTree : AlgoTreeDisplayMode.RootAndLeafNodes;
set => _show_full_algo_tree.value = value == AlgoTreeDisplayMode.FullTree;
}
public IEnumerable parameters {
get {
yield return _show_full_algo_tree;
}
}
public ICommand add_alert_cmd { get; }
public ICommand delete_alert_cmd { get; }
// TODO: make this generic (for now, all views orderbook settings can ignore it)
public ObservableCollection<OrderAlertVM> alerts { get; } = new ObservableCollection<OrderAlertVM>();
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings.general);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
CollectionHelper.add_or_update_existing(alerts,
grid_settings.predicate_animations,
pa => {
var vm = new OrderAlertVM();
vm.load_from(pa);
return vm;
},
(vm, pa) => vm.load_from(pa));
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
return new OrderBookGridSettings(gs, alerts.Select(vm => vm.create())) {
algo_tree_display_mode = algo_tree_display_mode
};
}
}
class OrderAlertVM : ViewModelBase, IValueFactory<OrderRecordTuplePredicateAnimations> {
SecurityCriteria _security_criteria;
SoundNotification.SystemSound _sound_type;
OrderRecordTuplePredicate.PredicateTypeOneofCase _predicate_type;
bool _is_enabled;
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
public SecurityCriteria security_criteria { get => _security_criteria; set => Set(ref _security_criteria, value); }
public SoundNotification.SystemSound sound_type { get => _sound_type; set => Set(ref _sound_type, value); }
public OrderRecordTuplePredicate.PredicateTypeOneofCase predicate_type { get => _predicate_type; set => Set(ref _predicate_type, value); }
public IEnumerable<OrderRecordTuplePredicate.PredicateTypeOneofCase> predicate_types { get; } =
new[] {
OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent,
OrderRecordTuplePredicate.PredicateTypeOneofCase.SentEvent
};
public OrderRecordTuplePredicateAnimations create() {
var pred = new OrderRecordTuplePredicate();
switch (predicate_type) {
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
pred.canceled_event = new OrderCanceledEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
pred.filled_event = new OrderFilledEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
pred.completed_event = new OrderCompletedEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.SentEvent:
pred.sent_event = new OrderSentEventPredicate {
security = security_criteria.is_defined() ?
new SecurityPredicate { is_value = security_criteria } : null
};
break;
}
return new OrderRecordTuplePredicateAnimations(
pred, null, null,
new[] {
new SoundNotification { system_sound = sound_type }
},
is_enabled);
}
public void load_from(OrderRecordTuplePredicateAnimations value) {
if (value.sound_notifications.Count == 0)
return;
is_enabled = value.is_enabled;
var animation = value.sound_notifications[0];
var pred = value.predicate_data;
if (animation.sound_type_case == SoundNotification.SoundTypeOneofCase.System)
sound_type = animation.system_sound.Value;
if (pred != null) {
predicate_type = pred.predicate_type_case;
switch (pred.predicate_type_case) {
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CanceledEvent:
security_criteria = pred.canceled_event.security?.is_value;
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.CompletedEvent:
security_criteria = pred.completed_event.security?.is_value;
break;
case OrderRecordTuplePredicate.PredicateTypeOneofCase.FilledEvent:
security_criteria = pred.filled_event.security?.is_value;
break;
}
}
}
}
}
@@ -0,0 +1,150 @@
<UserControl
x:Class="mbts.ui.spark.OrderBookSettingsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:bts.shell"
xmlns:orderbook="clr-namespace:bts.shell.orderbook"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Content="{Binding}"
>
<UserControl.Resources>
<usp:GridSettingsResources/>
</UserControl.Resources>
<UserControl.ContentTemplate>
<DataTemplate DataType="{x:Type orderbook:OrderBookSettingsVM}">
<Grid>
<Grid.Margin>
<Thickness Left="{StaticResource HorizontalPush2}" Right="{StaticResource HorizontalPush2}"/>
</Grid.Margin>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:ActionMenu
HorizontalAlignment="Right"
Margin="0,2,0,0"
ItemsSource="{Binding DataContext.action_menu_items, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
Style="{StaticResource SparkToolBarItem}"
/>
<TabControl x:Name="Tabs" HorizontalAlignment="Stretch" MinHeight="480" MinWidth="500" Margin="0,2,0,0" Padding="6">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="MinWidth" Value="60"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Grid">
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True" Margin="8,16,0,0">
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
<CollectionContainer Collection="{Binding DataContext.core_settings_vm.parameters, Source={u:DirectReference ParameterItemsControl}}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</TabItem>
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
<TabItem Header="Alerts" DataContext="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<c:DIYGrid
Rows="{Binding alerts}"
BorderBrush="Black"
BorderThickness="1"
RowsHorizontalScrollBarVisibility="Collapsed"
SelectionMode="Single"
>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<c:DIYGridColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
<c:DIYGridColumn key="IsEnabled" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="66" is_visible="True"/>
<c:DIYGridColumn key="Security" Header="Security" Width="130" is_visible="True"/>
<c:DIYGridColumn key="EventType" Header="Event Type" Width="120" is_visible="True"/>
<c:DIYGridColumn key="Sound" Header="Sound" Width="130" is_visible="True"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<Button
c:DIYGrid.ColumnKey="Delete"
HorizontalAlignment="Center"
Command="{Binding DataContext.delete_alert_cmd, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
CommandParameter="{Binding}"
Style="{StaticResource RoundCloseButton}"
ToolTip="Delete"
/>
<ToggleButton
c:DIYGrid.ColumnKey="IsEnabled"
IsChecked="{Binding is_enabled}"
Style="{StaticResource ToggleSwitch}"
/>
<ComboBox
c:DIYGrid.ColumnKey="EventType"
Margin="{StaticResource CellPadding}"
ItemsSource="{Binding predicate_types}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding predicate_type}"
/>
<c:ContractPickerPopup
c:DIYGrid.ColumnKey="Security"
Margin="{StaticResource CellPadding}"
AllowEmpty="True"
CloseOnOK="True"
CloseOnCancel="True"
MaxWidth="180"
DataContext="{Binding predicate}"
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
Header="{Binding security_criteria}"
ShowDialogSkirt="True"
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
SelectedCriteria="{Binding security_criteria, UpdateSourceTrigger=Explicit}"
>
<c:ContractPickerPopup.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</c:ContractPickerPopup.HeaderTemplate>
</c:ContractPickerPopup>
<ComboBox
x:Name="sound_dropdown"
c:DIYGrid.ColumnKey="Sound"
Margin="{StaticResource CellPadding}"
ItemsSource="{u:EnumBindingSource Type={u:Nested shell:SoundNotification.SystemSound}, Except=None}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding sound_type}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownClosed">
<i:InvokeCommandAction Command="{x:Static c:SoundAnimationView.PreviewSoundCommand}" CommandParameter="{Binding SelectedItem, ElementName=sound_dropdown}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</ScrollViewer>
<Button Grid.Row="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalAlignment="Top" VerticalContentAlignment="Center" Command="{Binding add_alert_cmd}">
<TextBlock Margin="8,4" FontWeight="SemiBold">Add Alert</TextBlock>
</Button>
</Grid>
</TabItem>
</TabControl>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
@@ -0,0 +1,35 @@
using mbts.ui.converters;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for OrderBookSettingsView.xaml
/// </summary>
partial class OrderBookSettingsView : UserControl {
public static readonly IValueConverter
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
public OrderBookSettingsView() =>
InitializeComponent();
public static class VisualStateNames {
public static class DisplayName {
public const string GroupName = nameof(DisplayName);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class ColumnVisibilityToggle {
public const string GroupName = nameof(ColumnVisibilityToggle);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
}
@@ -1,133 +1,342 @@
using bts.collections;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
namespace bts.shell.pnl {
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
readonly SimpleGridSettingsVM _owner;
readonly PNLView.Columns _pnl_id;
object _selected_item;
string _display_name;
bool _is_visible;
double _width;
HorizontalAlignment _horizontal_alignment;
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
_owner = owner;
_pnl_id = id;
default_column_vm.PropertyChanged += echo;
void echo(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(is_font_weight_overridden):
case nameof(is_font_color_overridden):
RaisePropertyChanged(e.PropertyName);
break;
}
}
}
public PNLCellAppearanceOverrideVM default_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_column_vm { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_column_vm { get; } = new PNLCellAppearanceOverrideVM();
//
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
public object selected_item {
get => _selected_item;
set {
if (value != null)
Set(ref _selected_item, value);
}
}
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
public double width { get => _width; set => Set(ref _width, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
public bool is_font_color_overridden {
get => default_column_vm.is_font_color_overridden;
set => default_column_vm.is_font_color_overridden = value;
}
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
public GridColumnSettings create() {
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
result = default_column_vm.apply_to(result);
return result;
}
public void load_from(GridColumnSettings value) {
_assert_id(value.id);
default_column_vm.load(value);
display_name = value.display_name;
width = value.width;
is_visible = value.is_visible;
horizontal_alignment = value.horizontal_alignment;
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
_assert_id(value.id);
load_from(value);
if (group != null)
group_column_vm.load(group);
if (header != null)
header_column_vm.load(header);
rule_columns.SetItems(triggers.Select(kv => {
var rule_col = new PNLRuleColumnVM(kv.Key);
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
return rule_col;
}));
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
foreach (var col in rule_columns)
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
}
void _assert_id(string id) =>
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
public override string ToString() => "Default";
}
class PNLRuleColumnVM : ViewModelBase, IEquatable<PNLRuleColumnVM> {
public PNLRuleColumnVM(string rule_text) =>
this.rule_text = rule_text;
public string rule_text { get; }
public PNLCellAppearanceOverrideVM row_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM group_params { get; } = new PNLCellAppearanceOverrideVM();
public PNLCellAppearanceOverrideVM header_params { get; } = new PNLCellAppearanceOverrideVM();
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
row_params.load(row);
group_params.load(group);
header_params.load(header);
}
public bool Equals(PNLRuleColumnVM other) =>
other != null && rule_text == other.rule_text;
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
public override int GetHashCode() => rule_text.GetHashCode();
public override string ToString() => rule_text;
}
using bts.collections;
using bts.proto;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.ui.viewmodels;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
namespace bts.shell.pnl {
class PNLColumnSettingsModeVM : GridColumnSettingsVM {
readonly ObservableAggregateCollection<object> _aggregate_parameters = new ObservableAggregateCollection<object>();
IDisposable _param_sync;
List<(string rule, PNLCellModeVM vm)> _rules = new List<(string rule, PNLCellModeVM vm)>();
public PNLColumnSettingsModeVM(string mode) {
this.mode = mode;
show_horizontal_alignment = false;
show_width = false;
can_edit_header = false;
header.can_override = false;
parameter_group = new ParameterGroupVM(mode);
_param_sync = ObservableAutoWrapper.connect(parameters, parameter_group.parameters);
}
public ParameterGroupVM parameter_group { get; }
public string mode { get; }
public IEnumerable<(string rule, PNLCellModeVM vm)> rules => _rules;
public GridColumnSettings apply_to(GridColumnSettings target) {
return target.with(
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
}
public void load_rules(IReadOnlyDictionary<string, PNLAnimation> rules) {
Disposable.dispose(ref _param_sync);
_rules = rules.Select(
kv => {
var rule_vm = new PNLCellModeVM($"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
rule_vm.load(kv.Value);
return (kv.Key, rule_vm);
})
.ToList();
if (_rules.Count > 0) {
var row_defaults = new PNLCellModeVM("Default") { is_expanded = true };
parameter_group.parameters.SetItems(_rules.Select(r => r.vm).Prepend(row_defaults));
} else {
sync_params();
_param_sync = ObservableAutoWrapper.connect(parameters, parameter_group.parameters);
}
}
}
class PNLRulesVM : ParameterGroupVM {
PNLCellModeRuleSetVM _selected_cell_mode;
public PNLRulesVM(string display_name) : base(display_name) {
}
public PNLCellModeRuleSetVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
parameters.Clear();
var default_mode = new PNLCellModeRuleSetVM("Default");
default_mode.default_vm.load(value);
default_mode.group_vm.load(group);
default_mode.header_vm.load(header);
parameters.Add(default_mode);
foreach (var kv in triggers) {
var rule_vm = new PNLCellModeRuleSetVM($"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
rule_vm.default_vm.load(kv.Value);
if (group_triggers.TryGetValue(kv.Key, out var g)) {
rule_vm.group_vm.load(g);
}
if (header_triggers.TryGetValue(kv.Key, out var h))
rule_vm.header_vm.load(h);
parameters.Add(rule_vm);
}
if (selected_cell_mode == null)
selected_cell_mode = default_mode;
}
}
class PNLCellModeRuleSetVM : ParameterGroupVM {
PNLCellRuleModeVM _selected_cell_mode;
public PNLCellModeRuleSetVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(default_vm, group_vm, header_vm);
_selected_cell_mode = default_vm;
}
public PNLCellRuleModeVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
public PNLCellRuleModeVM default_vm { get; } = new PNLCellRuleModeVM("Row") { is_expanded = true };
public PNLCellRuleModeVM group_vm { get; } = new PNLCellRuleModeVM("Group Row");
public PNLCellRuleModeVM header_vm { get; } = new PNLCellRuleModeVM("Header Row");
}
class PNLCellRuleModeVM : ParameterGroupVM {
public PNLCellRuleModeVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(background, font_color, font_weight);
background.can_override = true;
font_color.can_override = true;
font_weight.can_override = true;
}
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM("Font Weight");
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
public PNLAnimation create(PNLView.Columns col_key) {
var rv = new PNLAnimation {
column = col_key
};
if (background.effective_is_overridden)
rv.background = background.override_value.rgb_color();
if (font_color.effective_is_overridden)
rv.foreground = font_color.override_value.rgb_color();
if (font_weight.effective_is_overridden)
rv.font_weight = font_weight.override_value.ToOpenTypeWeight();
return rv;
}
public void load(GridColumnSettings value) {
// font weight
font_weight.is_overridden = value.font_weight_override.HasValue;
if (font_weight.is_overridden)
font_weight.override_value = value.font_weight_override.Value;
// font color
font_color.is_overridden = value.font_color_override.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.font_color_override.Value;
// background
background.is_overridden = value.background_color_override.HasValue;
if (background.is_overridden)
background.override_value = value.background_color_override.Value;
}
public void load(PNLAnimation value) {
// font weight
font_weight.is_overridden = value.font_weight.HasValue && value.font_weight.Value > 0;
if (font_weight.is_overridden)
font_weight.override_value = FontWeight.FromOpenTypeWeight(value.font_weight.Value);
// font color
font_color.is_overridden = value.foreground.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.foreground.Value.argb_color();
// background
background.is_overridden = value.background.HasValue;
if (background.is_overridden)
background.override_value = value.background.Value.argb_color();
}
}
class PNLCellModeVM : ParameterGroupVM {
public PNLCellModeVM(string display_name, string rule = null) : base(display_name) {
parameters.AddRange(background, font_color, font_weight);
background.can_override = true;
font_color.can_override = true;
font_weight.can_override = true;
}
public string rule { get; }
public ColorOverrideParameterVM background { get; } = new ColorOverrideParameterVM("Background");
public ColorOverrideParameterVM font_color { get; } = new ColorOverrideParameterVM("Font Color");
public FontWeightOverrideParameterVM font_weight { get; } = new FontWeightOverrideParameterVM("Font Weight");
public GridColumnSettings apply_to(GridColumnSettings target) =>
target.with(
font_weight_override: new NullableFontWeightProto(font_weight.effective_is_overridden ? font_weight.override_value : (FontWeight?)null),
font_color_override: new NullableColorProto(font_color.effective_is_overridden ? font_color.override_value : (Color?)null),
background_color_override: new NullableColorProto(background.effective_is_overridden ? background.override_value : (Color?)null));
public PNLAnimation create(PNLView.Columns col_key) {
var rv = new PNLAnimation {
column = col_key
};
if (background.effective_is_overridden)
rv.background = background.override_value.rgb_color();
if (font_color.effective_is_overridden)
rv.foreground = font_color.override_value.rgb_color();
if (font_weight.effective_is_overridden)
rv.font_weight = font_weight.override_value.ToOpenTypeWeight();
return rv;
}
public void load(PNLAnimation value) {
// font weight
font_weight.is_overridden = value.font_weight.HasValue && value.font_weight.Value > 0;
if (font_weight.is_overridden)
font_weight.override_value = FontWeight.FromOpenTypeWeight(value.font_weight.Value);
// font color
font_color.is_overridden = value.foreground.HasValue;
if (font_color.is_overridden)
font_color.override_value = value.foreground.Value.argb_color();
// background
background.is_overridden = value.background.HasValue;
if (background.is_overridden)
background.override_value = value.background.Value.argb_color();
}
}
class PNLColumnSettingsVM : GridColumnSettingsVM {
readonly ObservableCollection<PNLColumnSettingsModeVM> _modes = new ObservableCollection<PNLColumnSettingsModeVM>();
readonly ParameterGroupVM _column_params_group = new ParameterGroupVM("Column Layout");
readonly ObservableAggregateCollection<object> _cell_appearance_groups = new ObservableAggregateCollection<object>();
public PNLColumnSettingsVM() : base(null) {
_column_params_group.parameters.AddRange(header, width, horizontal_alignment);
// hide header/width/align params in group/header vms
group_column_vm.can_edit_width = false;
group_column_vm.can_edit_header = false;
group_column_vm.show_horizontal_alignment = false;
group_column_vm.header.can_override = false;
group_column_vm.sync_params();
// header
header_column_vm.show_width = false;
header_column_vm.show_horizontal_alignment = false;
header_column_vm.header.can_override = false;
header_column_vm.sync_params();
// header
default_vm.show_width = false;
default_vm.show_horizontal_alignment = false;
default_vm.header.can_override = false;
default_vm.sync_params();
default_vm.parameter_group.is_expanded = true;
fonts_and_colors.parameters.Add(default_vm.parameter_group);
fonts_and_colors.parameters.Add(group_column_vm.parameter_group);
fonts_and_colors.parameters.Add(header_column_vm.parameter_group);
apply_flags(GridColumnSettingsFeatureFlags.None);
sync_params();
}
public ParameterGroupVM column_layout => _column_params_group;
public ParameterGroupVM fonts_and_colors { get; } = new ParameterGroupVM("Fonts/Colors");
public PNLColumnSettingsModeVM group_column_vm { get; } = new PNLColumnSettingsModeVM("Group Row");
public PNLColumnSettingsModeVM header_column_vm { get; } = new PNLColumnSettingsModeVM("Header Row");
public PNLColumnSettingsModeVM default_vm { get; } = new PNLColumnSettingsModeVM("Row");
protected override IEnumerable<object> _visible_parameters {
get {
yield return header;
yield return width;
yield return horizontal_alignment;
//yield return default_vm.parameter_group;
//yield return group_column_vm.parameter_group;
//yield return header_column_vm.parameter_group;
}
}
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
load_from(value);
if (group != null)
group_column_vm.load_from(group);
if (header != null)
header_column_vm.load_from(header);
default_vm.load_rules(triggers);
group_column_vm.load_rules(triggers);
header_column_vm.load_rules(triggers);
if (triggers == null || triggers.Count == 0) {
fonts_and_colors.parameters.SetItems(new[] { default_vm.parameter_group, group_column_vm.parameter_group, header_column_vm.parameter_group });
} else {
fonts_and_colors.parameters.Clear();
//var default_mode = new PNLCellModeRuleSetVM("Default");
//default_mode.default_vm.load(value);
//default_mode.group_vm.load(group);
//default_mode.header_vm.load(header);
//fonts_and_colors.parameters.Add(default_mode);
//foreach (var kv in triggers) {
// var rule_vm = new PNLCellModeRuleSetVM( $"When {DisplayNameHelper.expression_snake_to_title_case(kv.Key)}", kv.Key);
// rule_vm.default_vm.load(kv.Value);
// if (group_triggers.TryGetValue(kv.Key, out var g)) {
// rule_vm.group_vm.load(g);
// }
// if (header_triggers.TryGetValue(kv.Key, out var h))
// rule_vm.header_vm.load(h);
// fonts_and_colors.parameters.Add(rule_vm);
//}
var rule_set = new PNLRulesVM("Rules Poop");
rule_set.load_from(value, triggers, group, group_triggers, header_triggers, header);
fonts_and_colors.parameters.Add(rule_set);
}
}
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
var triggers = new Dictionary<string, (PNLAnimation row, PNLAnimation group, PNLAnimation header)>();
if (Enum.TryParse(id, out PNLView.Columns col_key)) {
foreach (var (rule, vm) in default_vm.rules)
triggers[rule] = (vm.create(col_key), null, null);
foreach (var (rule, vm) in group_column_vm.rules)
triggers.AddOrUpdate(rule, r => (null, vm.create(col_key), null), (r, tup) => (tup.row, vm.create(col_key), tup.header));
foreach (var (rule, vm) in header_column_vm.rules)
triggers.AddOrUpdate(rule, r => (null, null, vm.create(col_key)), (r, tup) => (tup.row, tup.group, vm.create(col_key)));
}
return triggers.Select(kv => (kv.Key, kv.Value.row, kv.Value.group, kv.Value.header));
}
public override string ToString() => "Default";
static IEnumerable<object> _standard_params() {
var header = new StringOverrideParameterVM("Header");
var width = new DoubleParameterVM("Width");
var horizontal_alignment = new HorizontalAlignmentParameterVM("Horizontal Alignment");
yield return header;
yield return width;
yield return horizontal_alignment;
}
static IEnumerable<object> _cell_appearance_params() {
var background = new ColorOverrideParameterVM("Background");
var font_color = new ColorOverrideParameterVM("Font Color");
var font_weight = new FontWeightOverrideParameterVM();
yield return background;
yield return font_color;
yield return font_weight;
}
}
}
@@ -1,68 +1,76 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
enum CellTypes {
Row,
Group,
Header
}
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new GridSettingsVM();
columns = core_settings_vm.columns.CastObservable<GridColumnSettingsVM, PNLColumnSettingsVM>();
//col_settings => {
// var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
// col_vm.load_from(col_settings);
// return col_vm;
// });
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public GridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
public IReadOnlyObservableCollection<PNLColumnSettingsVM> columns { get; }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public ObservableCollection<NamedVM<GridColumnSettingsVM>> cell_types { get; }
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.get_result();
// move anchor cols to the left, order wrt grouping hierarchy
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
for (int i = 0; i < s.grid.columns.Count; i++) {
@@ -75,146 +83,147 @@ namespace bts.shell.pnl {
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
// rule-based animations
var trigger_anims = columns
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: columns.Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: columns.Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load(value.grid, false);
using (core_settings_vm.begin_change_columns()) {
core_settings_vm.columns.SetItems(value.grid.columns.Select(c => {
var vm = new PNLColumnSettingsVM();
var key = EnumHelper.parse<ColumnKeys>(c.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
return vm;
}));
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
@@ -291,7 +291,7 @@ namespace bts.shell.pnl {
};
public void load_from(PNLSettings settings) {
selection_highlight = settings.grid.selection_highlight.to_brush();
selection_highlight = settings.grid.selection_highlight.to_argb_brush();
grid_background_color = settings.grid.background;
calculation_type = settings.calculation_type;
if (settings.filters != null) {
@@ -383,7 +383,7 @@ namespace bts.shell.pnl {
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors);
return new FillsVM(
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
@@ -393,7 +393,7 @@ namespace bts.shell.pnl {
// hide implicit cols
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
return new FillsVM(
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public FillsVM mk_fills_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
@@ -419,7 +419,7 @@ namespace bts.shell.pnl {
break;
}
return new FillsVM(
_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
_bus, _orders_model, state, src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
public OrderBookVM mk_orderbook_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
@@ -0,0 +1,359 @@
<UserControl
x:Class="mbts.ui.spark.PNLSettingsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:bts.shell"
xmlns:vm="clr-namespace:mbts.ui.viewmodels"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:pnl="clr-namespace:bts.shell.pnl"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Content="{Binding}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<usp:GridSettingsResources/>
<ResourceDictionary Source="/view/Controls/TabControls/HorizontalTabControl.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="CellTypesTabWidth">183</sys:Double>
<DataTemplate x:Key="RowCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
<TextBlock>Row Cell Appearance</TextBlock>
</DataTemplate>
<DataTemplate x:Key="GroupCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
<TextBlock>Group Cell Appearance</TextBlock>
</DataTemplate>
<DataTemplate x:Key="HeaderCellTypeTemplate" DataType="{x:Type pnl:PNLColumnSettingsVM}">
<TextBlock>Header Cell Appearance</TextBlock>
</DataTemplate>
<DataTemplate x:Key="ParametersListTemplate">
<ListBox VerticalAlignment="Top" MinWidth="426" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
<Style x:Key="ParameterGroupTabItem" TargetType="TabItem" BasedOn="{StaticResource LargeMenuControlBase}">
<Setter Property="Padding" Value="14,10,14,10"/>
<Setter Property="Background" Value="#FFE8E8E8"/>
<Setter Property="Width" Value="{StaticResource CellTypesTabWidth}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target.TemplatedParent, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{StaticResource Button.MouseOver.Storyboard}"/>
<VisualState Name="Pressed" Storyboard="{StaticResource Button.Pressed.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
Name="presenter"
Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ParameterGroupTabHeaderTemplate">
<TextBlock x:Name="text" FontSize="13" Text="{Binding name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TabItem}}" Value="True">
<Setter TargetName="text" Property="TextBlock.FontWeight" Value="Bold"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Style x:Key="ParameterGroupTabControl" TargetType="TabControl">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ParameterGroupTabItem}"/>
<Setter Property="ItemTemplate" Value="{StaticResource ParameterGroupTabHeaderTemplate}"/>
<Setter Property="ItemsSource" Value="{Binding parameters}"/>
<Setter Property="ContentTemplate" Value="{StaticResource ParametersListTemplate}"/>
</Style>
<DataTemplate DataType="{x:Type pnl:PNLRulesVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Rectangle Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="{StaticResource CellTypesTabWidth}" Fill="#FFE8E8E8"/>-->
<c:HorizontalTabControl
Grid.Row="1"
Background="Transparent"
BorderThickness="0,1,0,0"
VerticalAlignment="Stretch"
ItemsSource="{Binding parameters}"
SelectedItem="{Binding selected_cell_mode}"
DisplayMemberPath="name"
>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource ParameterGroupTabItem}">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="4"/>
<Setter Property="Width" Value="{x:Static sys:Double.NaN}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ItemsControl ItemsSource="{Binding parameters}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<!--<Setter Property="BorderBrush" Value=""/>-->
<Setter Property="BorderThickness" Value="1,1,1,0"/>
<Setter Property="Padding" Value="4,6,4,8"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
</c:HorizontalTabControl>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type pnl:PNLColumnSettingsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Rectangle Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="{StaticResource CellTypesTabWidth}" Fill="#FFE8E8E8"/>-->
<!--<TabControl
Grid.Row="1"
ItemsSource="{Binding cell_modes}"
SelectedItem="{Binding selected_cell_mode}"
Style="{StaticResource ParameterGroupTabControl}"
/>-->
<ContentControl Content="{Binding column_layout}" ContentTemplate="{StaticResource HeaderedParameterGroup}"/>
<ContentControl Grid.Row="1" Content="{Binding fonts_and_colors}" ContentTemplate="{StaticResource HeaderedParameterGroup}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type sp:GridColumnSettingsVM}">
<ListBox VerticalAlignment="Top" MinWidth="426" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
<DataTemplate x:Key="HeaderedCellAppearanceContent" DataType="{x:Type sp:GridColumnSettingsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold">
Cell Appearance
</TextBlock>
<ListBox Grid.Row="1" VerticalAlignment="Top" ItemsSource="{Binding parameters}" Style="{StaticResource NakedListBox}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NakedListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.ContentTemplate>
<DataTemplate DataType="{x:Type pnl:PNLSettingsVM}">
<Grid>
<Grid.Margin>
<Thickness Left="{StaticResource HorizontalPush2}" Right="{StaticResource HorizontalPush2}"/>
</Grid.Margin>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:ActionMenu
Panel.ZIndex="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,2,0,0"
ItemsSource="{Binding DataContext.action_menu_items, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}"
Style="{StaticResource SparkToolBarItem}"
/>
<TabControl x:Name="Tabs" Background="Transparent" HorizontalAlignment="Stretch" MinHeight="480" MinWidth="600" Margin="0,2,0,0" Padding="6">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="MinWidth" Value="60"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Grid">
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True" Margin="8,16,0,0" ItemsSource="{Binding core_settings_vm.parameters}"/>
</TabItem>
<TabItem Header="Grouping">
<GroupBox Grid.Column="3" Grid.RowSpan="3" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<GroupBox.Resources>
<u:CursorsEx/>
</GroupBox.Resources>
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock>Grouping</TextBlock>
<c:HelpButton>
<c:HelpButton.Resources>
<Style x:Key="HelpButtonToolTipStyleKey" TargetType="ToolTip">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run>Check the group parameters you would like to use.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: drag/drop the items below to change the grouping hierarchy.</Run>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</c:HelpButton.Resources>
</c:HelpButton>
<ItemsControl ItemsSource="{Binding anchor_item_headers}" ItemsPanel="{StaticResource HorizontalItemsPanelTemplate}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="4,0,0,0" Content="{Binding key}" ContentTemplate="{StaticResource EnumDescriptionTemplate}" IsChecked="{Binding include}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</GroupBox.Header>
<ItemsControl
HorizontalAlignment="Left"
VerticalAlignment="Top"
AllowDrop="True"
ItemsSource="{Binding anchor_items}"
ItemTemplate="{DynamicResource AnchorItemTemplate}"
ItemsPanel="{StaticResource HorizontalItemsPanelTemplate}"
Loaded="_pnl_anchor_items_loaded"
/>
</GroupBox>
</TabItem>
<TabItem Header="Columns">
<Grid Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<!--<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush4}}"/>-->
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<c:DragDropListBox
x:Name="ColumnListBox"
DataContext="{Binding core_settings_vm}"
SelectedItem="{Binding current_column}"
SelectItemOnDrop="True"
Style="{DynamicResource ColumnsListBox}"
/>
<!--<ListBox
x:Name="CellTypeListBox"
Grid.Column="1"
Width="165"
ItemsSource="{u:EnumBindingSource {x:Type pnl:CellTypes}}"
SelectedIndex="0"
Style="{StaticResource LargeMenuListBox}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringFormat='{}{0} Cell Appearance'}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Background>
<SolidColorBrush Color="#FFDDDDDD"/>
</ListBox.Background>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource LargeMenuListBoxItemBase}">
<Setter Property="Padding" Value="8"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="{Binding IsSelected, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanFontWeightConverter}}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>-->
<GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="5" Background="Transparent"/>
<ScrollViewer Grid.Column="2" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" Content="{Binding core_settings_vm.current_column}">
<!--<MultiBinding Converter="{x:Static usp:PNLSettingsView.ColumnContentConverter}">
<Binding Path="SelectedItem" ElementName="ColumnListBox"/>
<Binding Path="SelectedItem" ElementName="CellTypeListBox"/>
</MultiBinding>-->
<ScrollViewer.MaxHeight>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=usp:PNLSettingsView}">
<Binding.Converter>
<cv:DoubleSubtractionConverter Value="26"/>
</Binding.Converter>
</Binding>
</ScrollViewer.MaxHeight>
</ScrollViewer>
</Grid>
</TabItem>
</TabControl>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
@@ -0,0 +1,70 @@
using bts.shell.pnl;
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.viewmodels;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for PNLSettingsView.xaml
/// </summary>
partial class PNLSettingsView : UserControl {
public static readonly IValueConverter
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
public static IMultiValueConverter
ColumnContentConverter => ValueConverter.create<PNLColumnSettingsVM, CellTypes, GridColumnSettingsVM>((col, cell) => {
if (col != null) {
switch (cell) {
case CellTypes.Row: return col;
case CellTypes.Group: return col.group_column_vm;
case CellTypes.Header: return col.header_column_vm;
}
}
return null;
});
public PNLSettingsView() =>
InitializeComponent();
private void _pnl_anchor_items_loaded(object sender, System.Windows.RoutedEventArgs e) {
}
public static class VisualStateNames {
public static class DisplayName {
public const string GroupName = nameof(DisplayName);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class ColumnVisibilityToggle {
public const string GroupName = nameof(ColumnVisibilityToggle);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
class PNLModalColumnSettingsVM {
public PNLColumnSettingsVM column { get; }
}
class PNLParameterContentTemplateSelector : DataTemplateSelector {
public DataTemplate ParameterList { get; set; }
public DataTemplate TabGroups { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item is ParameterGroupVM g && g.enable_tab_display)
return TabGroups;
return ParameterList;
}
}
}
+1 -3
View File
@@ -36,8 +36,6 @@ namespace mbts.spark {
public AlgoDefinitionMetadata? root_algo_meta =>
_order_tuple.HasValue ? _order_tuple.Value.root.meta : null;
public OrderSecurityAlgo? root_order_security => _order_tuple?.root;
public string account {
get {
var account_string = string.Empty;
@@ -87,4 +85,4 @@ namespace mbts.spark {
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
}
+2 -4
View File
@@ -158,9 +158,7 @@ namespace mbts.spark {
public static string display_name(this OrderSecurity order_tuple) =>
display_name(order_tuple.order, order_tuple.algo);
public static string display_name(OrderRecordImmutable order, AlgoData? algo, SecurityImmutable? security = null) {
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader && security.HasValue)
return security.Value.ToString();
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
if (algo.HasValue) {
return algo.Value.name;
} else if (order.HasDef && order.Def.HasCustomDef) {
@@ -252,7 +250,7 @@ namespace mbts.spark {
order_type.ToString().ToUpper();
public static string display_name(this OrderSecurityAlgo order_sec_algo) =>
display_name(order_sec_algo.order, order_sec_algo.algo, order_sec_algo.security);
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();
-1
View File
@@ -210,7 +210,6 @@ namespace mbts.spark {
// manual fills
on_manual_fills_securities = _position_service.subscribei_manual_fills()
.Naggle(interval, scheduler)
.Filter(f => f.FillType == ManualFill.Types.FillType.Local)
.join(sstore, mf => mf.SecurityKey)
.Table().Cache();
var manual_fill_tuples_account_join = on_manual_fills_securities.LeftJoin(
@@ -1,80 +1,74 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace mbts {
static class OrderSentEventPredicateVMFactory {
public static Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> get_oneofs(IBus bus) =>
new Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> {
{ OrderSentEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderSentEventPredicate>(p => p.security = null) },
{ OrderSentEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderSentEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
(p, s) => {
if (p.security == null) {
p.security = new SecurityPredicate();
}
p.security.is_value = s;
})
}
};
}
[ProtoContract]
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
static readonly int _type_hash = typeof(OrderSentEventPredicate).GetHashCode();
// arbitrary short time period (can override in config)
static readonly TimeSpan _stale_order_threshold = TimeSpan.FromSeconds(config.Configuration.get<bts.config.OrderAlerts>().stale_order_threshold_seconds);
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
[Description("choose securities")]
SecurityOrder
}
[ProtoMember(1)]
public SecurityPredicate security { get; set; }
public PredicateTypeOneofCase predicate_type_case {
get {
if (security != null)
return PredicateTypeOneofCase.SecurityOrder;
return PredicateTypeOneofCase.AnyOrder;
}
}
public IPredicate<OrderSecurity> create(object obj) =>
create_predicate((INotificationServiceDataCache)obj);
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
var sec_pred = security?.create();
return PredicateBuilder.create<OrderSecurity>(os =>
// verify security
(sec_pred == null || (os.security.HasValue && sec_pred.trigger(os.security.Value))) &&
// verify not stale
((DateTime.Now - os.order.created()) < _stale_order_threshold) &&
// verify order hasn't been added yet
// Note: the notification service should offer a way to connect predicates so that they only receive items in TableUpdate.added,
// however, the notification service predated TableUpdate.Changes, so for now, just check that the history.Count == 1)
(data_cache.order_history.TryGetValue(os.order.Key, out var hist) && hist.Count == 1));
}
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
throw new NotImplementedException();
public bool Equals(OrderSentEventPredicate other) =>
other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) =>
Equals(obj as OrderSentEventPredicate);
public override int GetHashCode() =>
EqualityComparer.auto_phase(_type_hash, security?.GetHashCode() ?? 0);
}
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using ProtoBuf;
using System;
using System.ComponentModel;
namespace mbts {
[ProtoContract]
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
[Description("choose securities")]
SecurityOrder
}
[ProtoMember(1)]
public SecurityPredicate security { get; set; }
public PredicateTypeOneofCase predicate_type_case {
get {
if (security != null)
return PredicateTypeOneofCase.SecurityOrder;
return PredicateTypeOneofCase.AnyOrder;
}
}
public IPredicate<OrderSecurity> create(object obj) =>
create_predicate((INotificationServiceDataCache)obj);
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) =>
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Working, security, data_cache);
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
new MyToaster(security?.create(), data_cache);
public bool Equals(OrderSentEventPredicate other) => other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) => Equals(obj as OrderSentEventPredicate);
public override int GetHashCode() => security == null ? 0 : security.GetHashCode();
static bool _is_canceled(OrderRecordImmutable o) =>
o.HasState && o.State.Status == OrderStatus.Working;
class MyToaster : INotificationPredicate<OrderSecurity>, IEquatable<MyToaster> {
readonly IPredicate<SecurityImmutable> _security_predicate;
readonly INotificationServiceDataCache _data_cache;
public MyToaster(IPredicate<SecurityImmutable> security_predicate, INotificationServiceDataCache data_cache) {
_security_predicate = security_predicate;
_data_cache = data_cache;
}
public IToastNotification trigger(OrderSecurity order) =>
OrderEventPredicateHelper.check_order_transition_trigger(order, _is_canceled, _security_predicate, _data_cache, out var oldval) ?
ToastNotifications.order_status_changed(order.order.Key,
OrderEventPredicateHelper.get_nullable_status(oldval), OrderStatus.Working, order.security) :
ToastNotifications.empty;
bool IPredicate<OrderSecurity>.trigger(OrderSecurity order) =>
OrderEventPredicateHelper.check_order_transition_predicate(order, _is_canceled, _security_predicate, _data_cache);
public bool Equals(MyToaster other) =>
other != null && EqualityComparer.null_check_equals(_security_predicate, other._security_predicate);
public override bool Equals(object obj) =>
Equals(obj as MyToaster);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash((int)OrderStatus.Working, _security_predicate == null ? 0 : _security_predicate.GetHashCode());
}
}
}
@@ -1,61 +1,15 @@
using bts.api;
using bts.api.orderman;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
namespace mbts {
static class OrderRecordTuplePredicateVMFactory {
// TODO: uncomment the remaining cases when ready
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
{ PTOC.SentEvent, wrap(rp => rp.sent_event, (rp, p) => rp.sent_event = p, p => p.predicate_type_case, OrderSentEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
//{ PTOC.Security, wrap(rp => rp.security, (rp, p) => rp.security = p, p => p.predicate_type_case, SecurityCriteriaVMFactory.get_oneofs(bus)) },
//{ PTOC.Time, wrap(rp => rp.time_created, (rp, p) => rp.time_created = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
//{ PTOC.LastChange, wrap(rp => rp.last_change, (rp, p) => rp.last_change = p, t => t.predicate_type_case, TimePredicateVMFactory.get_oneofs()) },
//{ PTOC.User, wrap(rp => rp.user, (rp, p) => rp.user = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
// RXCollection.Create(orders_model.factory.subscribe_user_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
//{ PTOC.Account, wrap(rp => rp.account, (rp, p) => rp.account = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs<uint, uint, uint?, uint[], DiscreteUIntPredicate>(
// RXCollection.Create(orders_model.factory.subscribe_account_ids().Select(tups => tups.Select(convert_name_id))), id => id, id => id.Value, id => id, ids => ids, ids => ids.ToArray())) },
//{ PTOC.Exchange, wrap(rp => rp.exchange, (rp, p) => rp.exchange = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<Venue, ExchangePredicate>(EnumHelper.get_values_except(Venue.Unset))) },
//{ PTOC.TimeInForce, wrap(rp => rp.time_in_force, (rp, p) => rp.time_in_force = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<TimeInForce, TimeInForcePredicate>(EnumHelper.get_values_except(TimeInForce.Undefined))) },
//{ PTOC.Side, wrap(rp => rp.side, (rp, p) => rp.side = p, p => p.predicate_type_case, SidePredicateVMFactory.get_oneofs()) },
//{ PTOC.Price, wrap(rp => rp.price, (rp, p) => rp.price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.Size, wrap(rp => rp.quantity, (rp, p) => rp.quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.Filled, wrap(rp => rp.filled_quantity, (rp, p) => rp.filled_quantity = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.AvgFilled, wrap(rp => rp.avg_price, (rp, p) => rp.avg_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.LastPrice, wrap(rp => rp.last_price, (rp, p) => rp.last_price = p, p => p.predicate_type_case, DoublePredicateVMFactory.get_oneofs()) },
//{ PTOC.OrderType, wrap(rp => rp.order_type, (rp, p) => rp.order_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderType, OrderTypePredicate>(EnumHelper.get_values_except(OrderType.None))) },
//{ PTOC.SubType, wrap(rp => rp.order_sub_type, (rp, p) => rp.order_sub_type = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderSubType, OrderSubTypePredicate>(EnumHelper.get_values<OrderSubType>())) },
//{ PTOC.Status, wrap(rp => rp.order_status, (rp, p) => rp.order_status = p, p => p.predicate_type_case, DiscreteValuesPredicateVMFactory.get_oneofs_value_type<OrderStatus, OrderStatusPredicate>(EnumHelper.get_values<OrderStatus>())) },
//{ PTOC.LastMessage, wrap(rp => rp.last_message, (rp, p) => rp.last_message = p, p => p.predicate_type_case, StringPredicateVMFactory.get_oneofs()) },
//{ PTOC.SecurityKey, wrap(rp => rp.security_key, (rp, p) => rp.security_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.OrderKey, wrap(rp => rp.order_key, (rp, p) => rp.order_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
//{ PTOC.ParentKey, wrap(rp => rp.parent_key, (rp, p) => rp.parent_key = p, p => p.predicate_type_case, UIntPredicateVMFactory.get_oneofs()) },
};
//IViewValue<uint> convert_name_id((string name, uint id) tup) =>
// new ViewValueTuple<uint>(tup.id, tup.name);
ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate> wrap<TChildPredicate, TChildPredicateOneof>(
Func<OrderRecordTuplePredicate, TChildPredicate> get,
Action<OrderRecordTuplePredicate, TChildPredicate> set,
Func<TChildPredicate, TChildPredicateOneof> key,
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
//
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
}
}
[ProtoContract]
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
SecurityPredicate _security;
@@ -117,14 +71,14 @@ namespace mbts {
OrderKey,
[PredicateCase(is_toastable:false)]
ParentKey,
[PredicateCase("When order is sent", PredicateCategory.Event)]
SentEvent,
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
[PredicateCase("Order Filled", PredicateCategory.Event)]
FilledEvent,
[PredicateCase("When order is completed", PredicateCategory.Event)]
[PredicateCase("Order Completed", PredicateCategory.Event)]
CompletedEvent,
[PredicateCase("When order is canceled", PredicateCategory.Event)]
[PredicateCase("Order Canceled", PredicateCategory.Event)]
CanceledEvent,
[PredicateCase("Order Sent", PredicateCategory.Event)]
SentEvent,
[PredicateCase(is_toastable:false)]
AttachedTargetOrderKey
}
@@ -369,6 +323,7 @@ namespace mbts {
[ProtoContract]
class OrderRecordTuplePredicateAnimations : IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate> {
const bool _default_is_enabled = true;
[ProtoMember(1)]
OrderRecordTuplePredicate _predicate;
[ProtoMember(2)]
@@ -377,19 +332,31 @@ namespace mbts {
List<RowAnimation> _row_animations;
[ProtoMember(4)]
List<SoundNotification> _sound_animations;
// is_enabled
[ProtoMember(5)]
[DefaultValue(_default_is_enabled)]
bool _is_enabled;
public OrderRecordTuplePredicateAnimations() { }
public OrderRecordTuplePredicateAnimations() =>
_is_enabled = _default_is_enabled;
public OrderRecordTuplePredicateAnimations(OrderRecordTuplePredicate predicate, IEnumerable<CellAnimation> cell_animations, IEnumerable<RowAnimation> row_animations, IEnumerable<SoundNotification> sound_animations) {
public OrderRecordTuplePredicateAnimations(
OrderRecordTuplePredicate predicate,
IEnumerable<CellAnimation> cell_animations,
IEnumerable<RowAnimation> row_animations,
IEnumerable<SoundNotification> sound_animations,
bool is_enabled = true) {
_predicate = predicate;
_cell_animations = new List<CellAnimation>(cell_animations);
_row_animations = new List<RowAnimation> ( row_animations );
_sound_animations = new List<SoundNotification> ( sound_animations );
_cell_animations = new List<CellAnimation>(cell_animations.empty_if_null());
_row_animations = new List<RowAnimation>(row_animations.empty_if_null());
_sound_animations = new List<SoundNotification>(sound_animations.empty_if_null());
_is_enabled = is_enabled;
}
public OrderRecordTuplePredicate predicate_data => _predicate;
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
public bool is_enabled => _is_enabled;
}
}
}
+3 -1
View File
@@ -44,6 +44,8 @@
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/view/Controls/ColorPicker/TabularColorPicker.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="ScrollViewer">
@@ -559,4 +561,4 @@
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>
@@ -7,7 +7,7 @@ namespace mbts.ui {
if (style == null) {
if (!allow_null)
throw new ArgumentException(nameof(style));
} else if (style.TargetType != expected)
} else if (!style.TargetType.IsAssignableFrom(expected))
throw WPFExceptionBuilder.style_target_type(expected, style.TargetType);
}
@@ -1,65 +0,0 @@
using System;
namespace mbts.ui.utility.text {
/// <summary>
/// Contains a font family, size, and foreground (uint).
/// <para/> NOTE: does not account for font weight, or typeface.
/// </summary>
struct FontParams : IEquatable<FontParams> {
public readonly FontFamily font_family;
public readonly double font_size;
public readonly uint foreground;
public FontParams(FontFamily font_family, double font_size, uint foreground) {
this.font_family = font_family;
this.font_size = font_size;
this.foreground = foreground;
}
public bool Equals(FontParams other) =>
other.font_family == font_family &&
other.font_size == font_size &&
other.foreground == foreground;
public override bool Equals(object obj) =>
obj is FontParams fsk && Equals(fsk);
public override int GetHashCode() =>
font_family.GetHashCode() ^
font_size.GetHashCode() ^
foreground.GetHashCode();
public static bool operator ==(FontParams x, FontParams y) =>
x.Equals(y);
public static bool operator !=(FontParams x, FontParams y) =>
!x.Equals(y);
}
struct FontParamsKey<T> where T : struct, IEquatable<T> {
public readonly T key;
public readonly FontParams font_params;
public FontParamsKey(T key, FontParams font_params) {
this.key = key;
this.font_params = font_params;
}
public bool Equals(FontParamsKey<T> other) =>
other.key.Equals(key) &&
other.font_params == font_params;
public override bool Equals(object obj) =>
obj is FontParamsKey<T> fpk && Equals(fpk);
public override int GetHashCode() =>
key.GetHashCode() ^
font_params.GetHashCode();
public static bool operator ==(FontParamsKey<T> x, FontParamsKey<T> y) =>
x.Equals(y);
public static bool operator !=(FontParamsKey<T> x, FontParamsKey<T> y) =>
!x.Equals(y);
}
}
@@ -1,36 +1,13 @@
using System;
using System.Linq;
using System.Linq;
namespace mbts {
public static class DoubleHelper {
public static bool any_nan_or_infinity(params double[] values) {
for (int i = 0; i < values.Length; i++) {
if (double.IsNaN(values[i]) || double.IsInfinity(values[i]))
public static bool AnyNaN(params double[] values) {
for (int i = 0; i < values.Length; i++)
if (double.IsNaN(values[i]))
return true;
}
return false;
}
public static bool are_close(double v1, double v2, double threshold) =>
!any_nan_or_infinity(v1, v2, threshold) && Math.Abs(threshold) > Math.Abs(v1 - v2);
// bc double.IsNaN gets annoying write, and a lazy programmer is a happy programmer ;)
public static bool is_nan(this double value) =>
double.IsNaN(value);
/// <summary>
/// If the specified value is a <see cref="double"/>, <see cref="int"/>, or <see cref="uint"/>, returns the equivalent <see cref="double"/>; otherwise, returns <see cref="double.NaN"/>.
/// </summary>
/// <param name="value">The value to convert to a <see cref="double"/>.</param>
/// <returns>The <see cref="double"/> equivalent value if the cast succeeds, otherwise <see cref="double.NaN"/>.</returns>
public static double nan_cast(object value) {
// devs: add numeric type checks as needed
if (value is double d) return d;
if (value is int i) return i;
if (value is uint u) return u;
return double.NaN;
}
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
@@ -1,4 +1,5 @@
using bts.shell.pnl;
using bts.shell.orderbook;
using bts.shell.pnl;
using bts.utils;
using mbts.ui.settings;
using System;
@@ -15,6 +16,7 @@ namespace bts.settings {
// determine the various windows to which such rules apply.
interface IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IOrderBookGridSettingsMigrationRule : ISettingsMigrationRule<OrderBookGridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
@@ -1,5 +1,6 @@
using bts;
using bts.settings;
using bts.shell.orderbook;
using bts.shell.pnl;
using bts.utils;
using mbts.spark;
@@ -28,6 +29,9 @@ namespace mbts.persist {
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, OrderBookGridSettings>
_orderbook_propi = ImmutableProperty.create<UserSettings, OrderBookGridSettings>(us => us.order_book_grid, (us, gs) => us.with(order_book_grid: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
@@ -35,6 +39,7 @@ namespace mbts.persist {
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
readonly IEnumerable<IFillsGridSettingsMigrationRule> _fills_migration_rules;
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
readonly IEnumerable<IOrderBookGridSettingsMigrationRule> _orderbook_migration_rules;
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
@@ -47,6 +52,7 @@ namespace mbts.persist {
IUserPersist persist,
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IOrderBookGridSettingsMigrationRule> orderbook_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
@@ -54,6 +60,7 @@ namespace mbts.persist {
_persist = persist;
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
_orderbook_migration_rules = orderbook_migration_rules ?? Enumerable.Empty<IOrderBookGridSettingsMigrationRule>();
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
@@ -130,6 +137,12 @@ namespace mbts.persist {
_mview_migration_rules,
_mview_propi);
public IWindowSettingsManager<OrderBookGridSettings> create_orderbook_settings_manager(OrderBookGridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_orderbook_migration_rules,
_orderbook_propi);
public IWindowSettingsManager<GridSettings> create_pandl_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
+3
View File
@@ -56,6 +56,9 @@ namespace bts {
public static T infer<T, A, B>(object value, Func<A, T> a, Func<B, T> b) =>
OneOf<A, B>.infer(value).Match(a, b);
public static T infer<T, A, B, C>(object value, Func<A, T> a, Func<B, T> b, Func<C, T> c) =>
OneOf<A, B, C>.infer(value).Match(a, b, c);
}
public class OneOf<A, B> {
@@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace mbts.utils {
public class ReverseComparer<T> : IComparer<T> {
private readonly IComparer<T> _wrapped;
public ReverseComparer(IComparer<T> wrapped) {
_wrapped = wrapped;
}
public int Compare(T x, T y) {
// reverse operands
return _wrapped.Compare(y, x);
}
}
}
@@ -23,6 +23,11 @@ namespace mbts.ui.shell {
void clear_error(string property);
}
interface IReadOnlyProperty<T> {
event PropertyChange<T> Changing, Changed;
T value { get; }
}
interface IProperty<T> : IObservable<(T oldval, T newval)> {
event PropertyChange<T> Changing, Changed;
T Value { get; set; }
@@ -36,11 +41,44 @@ namespace mbts.ui.shell {
public delegate void PropertyChange<T>(T oldval, T newval);
static class ValidationViewModelExtensions {
public static IDisposable echo<T>(this IRaiseNotifyPropertyChanged vm, IProperty<T> source_property, string target_property_name) {
source_property.Changed += on_changed;
return Disposable.Create(() => source_property.Changed -= on_changed);
void on_changed(T oldval, T newval) =>
vm.raise_notify_property_changed(target_property_name);
}
//public static IDisposable copy<T>(this IProperty<T> target, IProperty<T> source, string property_name) =>
// target.copy(source, property_name, property_name);
public static IDisposable copy<T>(this IProperty<T> target, IProperty<T> source) {
//var target_prop = target.GetType().GetProperty(target_property_name);
source.Changed += on_changed;
return Disposable.Create(() => source.Changed -= on_changed);
void on_changed(T oldval, T newval) {
target.Value = newval;
}
}
public static void assert(this IValidationViewModel vm, bool condition, string property, string message) {
if (condition) vm.clear_error(property);
else vm.set_error(property, message);
}
public static IDisposable subscribe_changed<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
property.Changed += on_changed;
return Disposable.Create(() => property.Changed -= on_changed);
void on_changed(T oldval, T newval) =>
observer.OnNext((oldval, newval));
}
public static IDisposable subscribe_changing<T>(this IProperty<T> property, IObserver<(T oldval, T newval)> observer) {
property.Changing += on_changing;
return Disposable.Create(() => property.Changing -= on_changing);
void on_changing(T oldval, T newval) =>
observer.OnNext((oldval, newval));
}
public static IObservable<bool> subscribe_has_errors(this INotifyDataErrorInfo vm) =>
Observable.Create<bool>(o => {
vm.ErrorsChanged += on_errors_changed;
@@ -53,6 +91,8 @@ namespace mbts.ui.shell {
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
Observable.Create<T>(o => property.Subscribe(ch => o.OnNext(ch.newval)));
//public static IDisposable S
public static IProperty<T> block_reentrancy<T>(this IProperty<T> source) =>
new GatedPropertyWrapper<T>(source);
@@ -89,6 +129,7 @@ namespace mbts.ui.shell {
return GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
});
}
protected void RaisePropertyChanged([CallerMemberName]string property="") {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
@@ -22,12 +22,20 @@ namespace bts.proto {
}
public Color color => _color.rgb_color();
public Color argb_color => Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
// TODO: get rid of this nonsense, all we need is a uint to store color & opacity
// (opacity is just the alpha bits of the color)
public Color argb_color =>
opacity == 1 ?
_color.argb_color() :
Color.FromArgb((byte)(MathUtils.within_range(0, 1, _opacity) * 255.0), color.R, color.G, color.B);
public double opacity => _opacity;
public ColorOpacityProto with(uint? color = null, double? opacity = null) =>
new ColorOpacityProto(color ?? _color, opacity ?? _opacity);
public SolidColorBrush to_argb_brush() =>
new SolidColorBrush(argb_color) { Opacity = _opacity };
public SolidColorBrush to_brush() =>
new SolidColorBrush(_color.rgb_color()) { Opacity = _opacity };
+10
View File
@@ -5,9 +5,19 @@
xmlns:ui="clr-namespace:mbts.ui"
>
<!--Control Base Colors-->
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
@@ -9,13 +9,14 @@
<SolidColorBrush x:Key="Ellipse.Pressed.Brush" Color="#FF3C77DD"/>
<SolidColorBrush x:Key="Ellipse.Disabled.Brush" Color="Gray"/>
<Style TargetType="c:ExpanderToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border>
<Border Background="{TemplateBinding Background}">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -1,11 +1,19 @@
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ExpanderToggleButton.xaml
/// </summary>
class ExpanderToggleButton : ToggleButton {
public static readonly RoutedCommand ToggleCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler ToggleExecuted = (s, e) => {
var bn = s as ToggleButton ?? e.Parameter as ToggleButton;
if (bn != null)
bn.SetCurrentValue(IsCheckedProperty, !bn.IsChecked.GetValueOrDefault());
};
static ExpanderToggleButton() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpanderToggleButton), new FrameworkPropertyMetadata(typeof(ExpanderToggleButton)));
}
@@ -1,4 +1,5 @@
using mbts.spark;
using bts.collections;
using mbts.spark;
using mbts.spark.utilities;
using ProtoBuf;
using System.Collections.Generic;
@@ -23,6 +24,7 @@ namespace mbts.ui.controls.color_picker {
yield return factory_order_colors_palette(order_factory);
yield return application_palette();
yield return bts_palette();
yield return system_palette();
yield return blank_palette();
}
@@ -109,6 +111,14 @@ namespace mbts.ui.controls.color_picker {
}
};
public static ColorPalette system_palette() =>
new ColorPalette {
group = "System",
colors = ColorUtils.SystemColors.WrapReadOnlySelector(
nc => new NamedColor(nc.value, nc.name))
.ToList()
};
public static ColorPalette blank_palette() =>
new ColorPalette {
group = "Custom",
@@ -1,11 +1,132 @@
using System;
using bts.collections;
using bts.utils;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Color = System.Windows.Media.Color;
using System.Windows.Media;
namespace mbts.ui.controls.color_picker {
public static class ColorUtils {
static class ColorUtils {
// (L)ight -> (H)ue comparer (hierarchical)
public static readonly IComparer<Color> LHComparer =
new ComparerBuilder<Color> {
ReverseComparer<double>.Default.wrap<double, Color>(brightness),
Comparer<double>.Default.wrap<double, Color>(hue)
}.to_comparer();
static readonly Named<Color>[]
_system_colors,
// 'pure' means no grays
_pure_web_colors,
_gray_web_colors,
_all_web_colors,
/// The 'grid' arrays below have the same colors as the arrays above, but are sorted differently to represent a grid structure:
/// - 140 web colors are split into 7 rows (20 items each),
/// - each row represents a 'hue' bucket
/// - the items within each row are sorted by lightness
/// Note: these 'grid-sorted' arrays serve as the [default]ItemsSource for the <see cref="TabularColorPicker"/>, which presents the items in a UniformGrid.
_pure_web_colors_grid,
_all_web_colors_grid;
static ColorUtils() {
var named_color_lh_comp = LHComparer.wrap<Color, Named<Color>>(nc => nc.value);
_system_colors = Array.ConvertAll(
typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public),
p => new Named<Color>(
DisplayNameHelper.camel_to_title_case(p.Name),
(Color)p.GetValue(null)));
Array.Sort(_system_colors, named_color_lh_comp);
_all_web_colors = Array.ConvertAll(
typeof(System.Drawing.Color)
.GetProperties(BindingFlags.Public | BindingFlags.Static),
p => {
var c = System.Drawing.Color.FromName(p.Name);
return new Named<Color>(p.Name, Color.FromArgb(c.A, c.R, c.G, c.B));
});
Array.Sort(_all_web_colors, named_color_lh_comp);
var all_web_colors_grid = new Named<Color>[_all_web_colors.Length - 1];
int adj = 0;
for (int i = 0; i < _all_web_colors.Length; i++) {
if (_all_web_colors[i].value.A == 0) {
adj++;
continue;
}
all_web_colors_grid[i - adj] = _all_web_colors[i];
}
SortColorGrid(ref all_web_colors_grid, nc => nc.value);
_all_web_colors_grid = all_web_colors_grid;
// break up into pure/gray
int num_gray = all_web_colors_grid.Count(nc => nc.value.is_gray());
_pure_web_colors = new Named<Color>[all_web_colors_grid.Length - num_gray];
_gray_web_colors = new Named<Color>[num_gray];
int gpos = 0;
int egpos = 0;
for (int i = 0; i < all_web_colors_grid.Length; i++) {
var nc = all_web_colors_grid[i];
if (nc.value.is_gray()) {
_gray_web_colors[gpos++] = nc;
} else {
_pure_web_colors[egpos++] = nc;
}
}
// gray
Array.Sort(_gray_web_colors, Comparer<double>.Default.wrap<double, Named<Color>>(nc => nc.value.brightness()));
// pure
var pure_web_colors_grid = _pure_web_colors.ToArray();
SortColorGrid(ref pure_web_colors_grid, nc => nc.value);
_pure_web_colors_grid = pure_web_colors_grid;
}
public static IReadOnlyList<Named<Color>> SystemColors =>
_system_colors;
public static IReadOnlyList<NamedColorVM> SystemColorVMs =>
_system_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<Named<Color>> WebColors =>
_all_web_colors;
public static IReadOnlyList<NamedColorVM> WebColorVMs =>
_all_web_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> GrayWebColorVMs =>
_gray_web_colors.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> PureWebColorVMs =>
_pure_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
public static IReadOnlyList<NamedColorVM> WebColorVMGrid =>
_all_web_colors_grid.WrapSelectorRO(_mk_readonly_vm);
public static void SortColorGrid<T>(ref T[] color_items, Func<T, Color> get_color, int column_count = 20) {
// Sorting colors is a lot harder than one would think, esp when the distribution of rgb values is non-linear.
// The questions one must ask are "how many shade groups should there be, and futhermore, how is a shade defined?"
// The logic employed below first breaks the colors up into 6 groups of hues (each occupying a range of 60/360 degrees of hue).
// A somewhat aribitrary offset of '10' is applied to achieve 'better looking' hue groups.
// -------------------------------------------------------------------
/// 1) sort by hue buckets (divide the 360 degrees of hue into 6 slices)
var hue_comp = Comparer<int>.Default.wrap<int, T>(ci => {
double raw_hue = get_color(ci).hue();
double adj_hue = raw_hue + 10;
if (adj_hue > 360) {
adj_hue -= 360;
}
int hue_bucket = (int)Math.Ceiling(adj_hue / 60.0);
return hue_bucket;
});
Array.Sort(color_items, hue_comp);
/// 2) break up the array into rows, and sort each row by lightness
var lightness_comp = Comparer<double>.Default.wrap<double, T>(ci => get_color(ci).brightness()).reverse();
for (int i = 0; i < color_items.Length; i+=column_count) {
int row_size = Math.Min(column_count, color_items.Length - i);
Array.Sort(color_items, i, row_size, lightness_comp);
}
}
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
@@ -15,7 +136,6 @@ namespace mbts.ui.controls.color_picker {
return Color.FromArgb(255, bytes[0], bytes[1], bytes[2]);
}
public static string ToHexString(this Color c) =>
"#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
@@ -170,17 +290,7 @@ namespace mbts.ui.controls.color_picker {
return Color.FromRgb((byte)(from.R + rdiff), (byte)(from.G + gdiff), (byte)(from.B + bdiff));
}
// https://stackoverflow.com/questions/5091455/web-color-list-in-c-sharp-application
public static List<Color> GetWebColors() {
Type colors = typeof(System.Drawing.Color);
PropertyInfo[] colorInfo = colors.GetProperties(BindingFlags.Public |
BindingFlags.Static);
List<Color> list = new List<Color>();
foreach (PropertyInfo info in colorInfo) {
var c = System.Drawing.Color.FromName(info.Name);
list.Add(Color.FromArgb(c.A, c.R, c.G, c.B));
}
return list;
}
static NamedColorVM _mk_readonly_vm(Named<Color> nc) =>
new NamedColorVM(new NamedColor(nc.value, nc.name), false);
}
}
@@ -5,6 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
>
<Control.Resources>
@@ -74,7 +75,29 @@
</Control.Resources>
<Control.Template>
<ControlTemplate TargetType="cp:ColorPickerControl">
<Grid>
<Grid DataContext="{Binding Model, RelativeSource={RelativeSource TemplatedParent}}">
<u:StateManager.VisualState>
<Binding Path="ShowControls" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="HideControls" ValueForTrue="ShowControls"/>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ControlStates">
<VisualState Name="ShowControls"/>
<VisualState Name="HideControls">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PaletteTabsGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SlidersGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="326" />
<ColumnDefinition Width="*" />
@@ -188,7 +211,7 @@
</TabItem>
</TabControl>
</Grid>
<Grid Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid x:Name="SlidersGrid" Grid.Column="1" Margin="{StaticResource PanelMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15" />
<ColumnDefinition Width="*" MinWidth="120" />
@@ -299,7 +322,7 @@
/>
</Grid>
<Grid Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
<Grid x:Name="PaletteTabsGrid" Grid.Row="1" Grid.ColumnSpan="3" Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
@@ -311,7 +334,12 @@
BorderThickness="2"
Background="{Binding result, Converter={StaticResource ColorToBrushConverter}}"
/>
<TabControl ItemsSource="{Binding palettes}" Grid.Column="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource PaletteTabItem}"/>
<TabControl
ItemsSource="{Binding palettes}"
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemContainerStyle="{StaticResource PaletteTabItem}"
/>
</Grid>
</Grid>
</ControlTemplate>
@@ -24,7 +24,6 @@ namespace mbts.ui.controls.color_picker {
partial class ColorPickerControl : Control {
const string _max_saturation_color_key = "MaxSaturationColor";
readonly PropertyUpdateGate _saturation_border_gate = new PropertyUpdateGate();
readonly ColorPickerVM _color_vm = new ColorPickerVM();
DataGrid _named_colors_grid;
Border _saturation_border;
@@ -34,7 +33,6 @@ namespace mbts.ui.controls.color_picker {
public ColorPickerControl() {
InitializeComponent();
DataContext = _color_vm;
CommandManager.AddExecutedHandler(this, executed_handler);
CommandManager.AddCanExecuteHandler(this, can_execute_handler);
@@ -50,10 +48,10 @@ namespace mbts.ui.controls.color_picker {
_named_colors_grid.CommitEdit();
} else if (e.Parameter is NamedColorVM vm) {
if (e.Command == ColorPickerSwatch.AssignColorCommand)
vm.assign_color(_color_vm.result);
vm.assign_color(Model.result);
else if (e.Command == ColorPickerSwatch.NameColorCommand) {
_named_colors_tab.IsSelected = true;
_color_vm.selected_named_color = vm;
Model.selected_named_color = vm;
_named_colors_grid.ScrollIntoView(vm);
_named_colors_grid.CurrentCell = new DataGridCellInfo(vm, _named_colors_grid.Columns[0]);
}
@@ -64,9 +62,26 @@ namespace mbts.ui.controls.color_picker {
e.CanExecute = true;
}
public event Action<Color> SelectedColorChanged;
public ColorPickerVM Model { get; } = new ColorPickerVM();
public static readonly DependencyProperty ShowControlsProperty =
WPFHelper.create_dp<bool, ColorPickerControl>(nameof(ShowControls), true);
public bool ShowControls {
get => (bool)GetValue(ShowControlsProperty);
set => SetValue(ShowControlsProperty, value);
}
#region SelectedColor
public static readonly DependencyProperty SelectedColorProperty =
WPFHelper.create_dp<Color, ColorPickerControl>(nameof(SelectedColor), (cp, val) => cp._color_vm.result = val);
WPFHelper.create_dp<Color, ColorPickerControl>(
nameof(SelectedColor),
(cp, val) => {
cp.Model.result = val;
cp.SelectedColorChanged?.Invoke(val);
});
public Color SelectedColor { get => (Color)GetValue(SelectedColorProperty); set => SetValue(SelectedColorProperty, value); }
@@ -95,17 +110,17 @@ namespace mbts.ui.controls.color_picker {
// b) destroy and recreate color_vm without breaking TabControl bindings, and ICollectionView source dependencies
void _on_palettes_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: _color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
case NotifyCollectionChangedAction.Remove: _color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
case NotifyCollectionChangedAction.Move: _color_vm.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
case NotifyCollectionChangedAction.Add: Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems); break;
case NotifyCollectionChangedAction.Remove: Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break;
case NotifyCollectionChangedAction.Move: Model.palettes.Move(e.OldStartingIndex, e.NewStartingIndex); break;
case NotifyCollectionChangedAction.Replace:
_color_vm.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
_color_vm.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
Model.palettes.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
Model.palettes.InsertRange(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
_color_vm.palettes.Clear();
Model.palettes.Clear();
if (sender is IEnumerable<ColorPaletteVM> items)
_color_vm.palettes.AddRange(items);
Model.palettes.AddRange(items);
break;
}
}
@@ -119,17 +134,17 @@ namespace mbts.ui.controls.color_picker {
_saturation_border = this.assert_template_child<Border>("_saturation_border");
_sample_image = this.assert_template_child<Image>("_sample_image");
// TabItems do not inherit DataContext properly from its visual parent, which breaks certain bindings
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = _color_vm });
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = _color_vm });
_color_vm.PropertyChanged += color_vm_property_changed;
_color_vm.result = SelectedColor;
_named_colors_grid.SetBinding(DataGrid.ItemsSourceProperty, new Binding(nameof(ColorPickerVM.all_colors_view)) { Source = Model });
_named_colors_grid.SetBinding(DataGrid.SelectedItemProperty, new Binding(nameof(ColorPickerVM.selected_named_color)) { Source = Model });
Model.PropertyChanged += color_vm_property_changed;
Model.result = SelectedColor;
IDisposable mouse_move_hook = null;
_saturation_border.MouseDown += saturation_border_mouse_down;
_saturation_border.MouseUp += saturation_border_mouse_up;
_template_hooks = Disposable.Create(() => {
_named_colors_grid.clear_bindings(DataGrid.ItemsSourceProperty, DataGrid.SelectedItemProperty);
_color_vm.PropertyChanged -= color_vm_property_changed;
Model.PropertyChanged -= color_vm_property_changed;
_saturation_border.MouseDown -= saturation_border_mouse_down;
_saturation_border.MouseUp -= saturation_border_mouse_up;
Disposable.dispose(ref mouse_move_hook);
@@ -138,7 +153,7 @@ namespace mbts.ui.controls.color_picker {
void color_vm_property_changed(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName) {
case nameof(ColorPickerVM.max_saturation): _update_saturation_img(); break;
case nameof(ColorPickerVM.result): SelectedColor = _color_vm.result; break;
case nameof(ColorPickerVM.result): SelectedColor = Model.result; break;
}
}
@@ -162,7 +177,7 @@ namespace mbts.ui.controls.color_picker {
var left_adj = ColorUtils.adjust_color(tr, Colors.White, left);
var result = ColorUtils.adjust_color(left_adj, Colors.Black, down);
using (_saturation_border_gate.update())
_color_vm.result = result;
Model.result = result;
}
}
@@ -173,7 +188,7 @@ namespace mbts.ui.controls.color_picker {
void _update_saturation_img() =>
_saturation_border_gate.try_update(() =>
Resources[_max_saturation_color_key] = _color_vm.max_saturation);
Resources[_max_saturation_color_key] = Model.max_saturation);
void _sample_image_click(BitmapSource img, Point pos) {
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/82a5731e-e201-4aaf-8d4b-062b138338fe/getting-pixel-information-from-a-bitmapimage?forum=wpf
@@ -188,9 +203,9 @@ namespace mbts.ui.controls.color_picker {
byte red = pixels[index];
byte green = pixels[index + 1];
byte blue = pixels[index + 2];
byte alpha = pixels[index + 3];
byte alpha = Model.alpha;
var color = Color.FromArgb(alpha, blue, green, red);
_color_vm.result = color;
Model.result = color;
}
void _sample_image_mouse_down(object sender, MouseButtonEventArgs e) {
@@ -0,0 +1,26 @@
<Window
x:Class="mbts.ui.controls.color_picker.ColorPickerDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.AllowResize="False"
Style="{StaticResource BTSDialog}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<cp:ColorPickerControl x:Name="color_picker"/>
<c:DialogSkirtOKCancel
x:Name="dialog_skirt"
Grid.Row="1"
BorderThickness="0"
/>
</Grid>
</Window>
@@ -0,0 +1,47 @@
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.extensions;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerControl.xaml
/// </summary>
partial class ColorPickerDialog : Window {
public ColorPickerDialog() =>
InitializeComponent();
public static Color pick_color_modal(Window owner = null, Color? init = null, ObservableCollection<ColorPaletteVM> palettes = null) {
var result = Colors.Transparent;
var cpd = new ColorPickerDialog {
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
if (palettes != null) {
cpd.color_picker.Palettes = palettes;
cpd.color_picker.ShowControls = true;
}
if (init.HasValue)
cpd.color_picker.SelectedColor = init.Value;
cpd.dialog_skirt.OKCommand = new DelegateCommand(() => {
result = cpd.color_picker.SelectedColor;
});
cpd.Owner = owner;
cpd.ShowDialog();
return result;
}
}
}
@@ -0,0 +1,115 @@
<UserControl
x:Class="mbts.ui.controls.color_picker.ColorPickerSuite"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusVisualStyle="{x:Null}"
Width="437"
>
<UserControl.Resources>
<!--<sys:Byte x:Key="LowAlpha">0x4C</sys:Byte>-->
<cv:MultiValueConverterPipe x:Key="LowAlphaToolTipVisibilityConverter">
<cv:DynamicIsLessThanConverter/>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Visibility.Collapsed}" ValueForTrue="{x:Static Visibility.Visible}"/>
</cv:MultiValueConverterPipe>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<c:TabularColorPicker
x:Name="_tabular_color_picker"
SelectedValuePath="color"
ItemsSource="{x:Static cp:ColorUtils.WebColorVMGrid}"
SelectedValue="{Binding Model.selected_named_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
SelectedOpacity="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
>
<c:TabularColorPicker.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource TabularColorPickerItemStyle}">
<!--ToolTips are not in the same VisualTree as their owner, and for some reason the ListBoxItem's Parent is null,
so there isn't a direct binding path from the ToolTip to the ColorPickerSuite.Model.opacity;
the 'Tag' binding is a workaround to make the ColorPickerSuite available to the ToolTip-->
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding}">
<ToolTip.ContentTemplate>
<DataTemplate>
<StackPanel>
<u:BindingProxy x:Name="LowAlphaProxy" Value="{DynamicResource LowAlpha}"/>
<TextBlock Text="{Binding name, Converter={StaticResource TitleCaseConverter}}" HorizontalAlignment="Left"/>
<TextBlock HorizontalAlignment="Left">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource LowAlphaToolTipVisibilityConverter}">
<Binding Path="PlacementTarget.Tag.Model.opacity" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
<Binding Path="Value" ElementName="LowAlphaProxy"/>
</MultiBinding>
</TextBlock.Visibility>
<Run>The color may be difficult to see because the opacity is low.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: Use the slider on the right to adjust the opacity level.</Run>
</TextBlock>
</StackPanel>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</c:TabularColorPicker.ItemContainerStyle>
</c:TabularColorPicker>
<Grid Grid.Column="1" Margin="4,0,0,0" Width="45" ToolTip="Opacity">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding StringFormat="P" Converter="{x:Static cv:ValueConverters.SliderPercentage}">
<Binding Path="Value" ElementName="_opacity_slider"/>
<Binding Path="Minimum" ElementName="_opacity_slider"/>
<Binding Path="Maximum" ElementName="_opacity_slider"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Slider
x:Name="_opacity_slider"
Grid.Row="1"
HorizontalAlignment="Center"
Orientation="Vertical"
Minimum="0"
Maximum="255"
Value="{Binding Model.opacity, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
/>
</Grid>
<c:PopupControl
Grid.Row="1"
Grid.ColumnSpan="2"
Padding="6"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
BorderThickness="0"
ShowExpanderArrow="False"
PopupPlacement="Bottom"
ButtonStyle="{StaticResource AddItemButton}"
Header="Custom"
>
<cp:ColorPickerControl
x:Name="_color_picker"
Padding="0,0,6,0"
Palettes="{Binding Model.palettes, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}}"
SelectedColor="{Binding Model.selected_color, RelativeSource={RelativeSource AncestorType=cp:ColorPickerSuite}, Mode=TwoWay}"
/>
</c:PopupControl>
</Grid>
</UserControl>
@@ -0,0 +1,144 @@
using bts.utils;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls.color_picker {
/// <summary>
/// Interaction logic for ColorPickerControl.xaml
/// </summary>
partial class ColorPickerSuite : UserControl {
public ColorPickerSuite() {
Model = new ColorModel(this);
InitializeComponent();
Model.on_palettes(Palettes);
Resources["SelectedOpacity"] = Model.opacity;
Model.PropertyChanged += (s, e) => {
if (e.PropertyName == nameof(Model.opacity))
Resources["SelectedOpacity"] = Model.opacity;
};
//Loaded += on_loaded;
//void on_loaded(object sender, RoutedEventArgs e) =>
// Model.initialize();
}
public static readonly DependencyProperty SelectedColorProperty =
WPFHelper.create_dp<Color, ColorPickerSuite>(
nameof(SelectedColor),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, v) => o.Model.selected_color = v);
public Color SelectedColor {
get => (Color)GetValue(SelectedColorProperty);
set => SetValue(SelectedColorProperty, value);
}
public static readonly DependencyProperty PalettesProperty =
WPFHelper.create_dp<ObservableCollection<ColorPaletteVM>, ColorPickerSuite>(
nameof(Palettes),
(o, v) => {
o.Model.on_palettes(v);
});
public ObservableCollection<ColorPaletteVM> Palettes {
get => GetValue(PalettesProperty) as ObservableCollection<ColorPaletteVM>;
set => SetValue(PalettesProperty, value);
}
public ColorModel Model { get; }
public class ColorModel : ViewModelBase {
readonly ColorPickerSuite _owner;
// Note: 'alpha' and 'opacity' and 'A' (when in the context of 'argb') may be used interchangebly in the comments below.
// -----------------------------------
/// There is an important difference between <see cref="selected_color"/> and <see cref="selected_named_color"/>.
/// The latter is bound to the SelectedValue of the <see cref="TabularColorPicker"/> (TCP), which does not have any opacity input controls.
/// while the former is manually to
/// For this reason, TCP must be immune to alpha channel adjustments, made in other controls (<see cref="ColorPickerSuite"/>, and <see cref="ColorPickerControl"/>, both have alpha/opacity sliders).
/// To achieve this affect, when
/// To achieve this affect, the latter's Alpha should always be 255 (100% opacity).
/// In the <see cref="selected_named_color"/> setter, only the RGB values are forwarded to the 'selected_color' property.
/// This 'alpha buffer' works both ways:
/// - it prevents the TCP from changing the selected_color's alpha to 1 everytime a tabular color item is selected.
/// - and it prevents the alpha sliders from changing TCP's SelectedValue everytime they are adjusted.
///
Color
_selected_color,
_selected_named_color;
IDisposable _palettes_hook;
Action _init;
public ColorModel(ColorPickerSuite owner) {
_owner = owner;
_init = () => {
/// This model demuxes color selection changes from 3 different controls, to a single dependency property (<see cref="SelectedColorProperty"/>).
/// Note the 3 controls are: <see cref="ColorPickerControl"/>, <see cref="TabularColorPicker"/>, <see cref="ColorPickerDialog"/>.
/// Each of the aforementioned controls is bound to one or more properties in this class (<see cref="selected_color"/>, <see cref="selected_named_color"/>, <see cref="palettes"/>).
/// In this design scheme, those controls are the binding targets, and this class the binding source.
/// As the 'binding source,' this class must initialize the 'binding targets' with the appropriate properties.
/// Otherwise, their bogus default values will be assigned to this class, and in turn used to update the binding source of the <see cref="SelectedColorProperty"/>.
/// In other words, the viewmodel's prop that is bound to <see cref="SelectedColorProperty"/> would be overwritten with the bogus default value.
/// To eschew this mess, the code below is executed before the other controls are allowed to affect the <see cref="selected_color"/> property.
/// Thus, they are initialized with the selected color provided by the binding source (aka viewmodel) of the <see cref="SelectedColorProperty"/>, rather than the other way around.
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
};
}
public Color selected_color {
get => _selected_color;
set {
InvokeOnce.invoke(ref _init);
var alpha = _selected_color.A;
if (Set(ref _selected_color, value)) {
_owner.SetCurrentValue(SelectedColorProperty, value);
if (_selected_color.A != alpha)
RaisePropertyChanged(nameof(opacity));
Set(ref _selected_named_color, value.with(255), nameof(selected_named_color));
}
}
}
public Color selected_named_color {
get => _selected_named_color;
set {
InvokeOnce.invoke(ref _init);
if (Set(ref _selected_named_color, value)) {
if (value != null)
selected_color = value.with(opacity);
}
}
}
public byte opacity {
get => _selected_color.A;
set => selected_color = _selected_color.with(value);
}
public ObservableCollection<ColorPaletteVM> palettes { get; } = new ObservableCollection<ColorPaletteVM>();
bool _is_initialized;
public void initialize() {
if (_is_initialized)
return;
_is_initialized = true;
_owner.GetBindingExpression(SelectedColorProperty)?.UpdateTarget();
_owner._color_picker.SelectedColorChanged += c => selected_color = c;
}
public void on_palettes(ObservableCollection<ColorPaletteVM> new_palettes) {
Disposable.dispose(ref _palettes_hook);
if (new_palettes != null)
_palettes_hook = ObservableAutoWrapper.connect(new_palettes, palettes);
}
}
}
}
@@ -1,28 +1,24 @@
<ListBox
<c:TabularColorPicker
x:Class="mbts.ui.controls.color_picker.ColorPickerSwatch"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
>
<ListBox.Resources>
<FrameworkElement.Resources>
<cp:NamedColorItemToolTipConverter x:Key="ToolTipConverter"/>
</ListBox.Resources>
<ListBox.ItemsPanel>
</FrameworkElement.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Cursor" Value="Hand"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
</ItemsControl.ItemsPanel>
<!--<ListBox.ItemTemplate>
<DataTemplate>
<Border
BorderBrush="Black"
@@ -40,5 +36,5 @@
</Border.ToolTip>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ListBox.ItemTemplate>-->
</c:TabularColorPicker>
Loaded 100 of 189 files, more files were not shown because too many files have changed in this diff. Show more