Compare commits
359
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ede2774783 | ||
|
|
9209561d20 | ||
|
|
2157e157d3 | ||
|
|
c78a13fe56 | ||
|
|
8034ad2a71 | ||
|
|
1f6835fc53 | ||
|
|
99f26667e7 | ||
|
|
a80bf72ee4 | ||
|
|
04b8f255b1 | ||
|
|
805f6a117a | ||
|
|
7b8d45c229 | ||
|
|
89d2652619 | ||
|
|
96790ea365 | ||
|
|
a97a1bd775 | ||
|
|
e69dc78969 | ||
|
|
824ff609b2 | ||
|
|
d5e07ae893 | ||
|
|
a66e164326 | ||
|
|
822850d557 | ||
|
|
a55bee9798 | ||
|
|
fb367f4b55 | ||
|
|
50fd62c834 | ||
|
|
f8e929aaa8 | ||
|
|
7f61c782a7 | ||
|
|
6586a884f4 | ||
|
|
a7edb1785f | ||
|
|
27b7aa4bf5 | ||
|
|
cbb1425354 | ||
|
|
2416bb376a | ||
|
|
fba6bd2d97 | ||
|
|
fe3377cc79 | ||
|
|
01fd435cde | ||
|
|
a053695f7c | ||
|
|
87b6032be7 | ||
|
|
885d670490 | ||
|
|
366a269dc2 | ||
|
|
86b9e2e6b4 | ||
|
|
d8336938e3 | ||
|
|
561b39dba0 | ||
|
|
824323f6eb | ||
|
|
d26dd10f11 | ||
|
|
ec4f6b16ca | ||
|
|
ecc96b24da | ||
|
|
6109862b37 | ||
|
|
aae27e4142 | ||
|
|
404bd9580a | ||
|
|
03bbb8a558 | ||
|
|
146dfada01 | ||
|
|
4caedf2fee | ||
|
|
05112b89a9 | ||
|
|
7d29ce3d46 | ||
|
|
09c57423a8 | ||
|
|
f61a1f94e2 | ||
|
|
0fc5046dec | ||
|
|
1ec41c531b | ||
|
|
0080947156 | ||
|
|
111c251809 | ||
|
|
9170637ac6 | ||
|
|
696f048c0c | ||
|
|
e5f7247a8e | ||
|
|
9a64b3331b | ||
|
|
a34483d45e | ||
|
|
14a2c4f76f | ||
|
|
cb97889d09 | ||
|
|
ac18e6a694 | ||
|
|
a6f9fbaa3c | ||
|
|
c66dfa85c1 | ||
|
|
3863140edf | ||
|
|
616d90d468 | ||
|
|
7ee6aed043 | ||
|
|
c9a10a1c37 | ||
|
|
025b49b7cd | ||
|
|
1b1583abda | ||
|
|
9d2f2888bb | ||
|
|
801b9622f8 | ||
|
|
a5df3f2da2 | ||
|
|
f3a692bda2 | ||
|
|
6c3bdbdc34 | ||
|
|
d4749dd381 | ||
|
|
a008f4d0fd | ||
|
|
0ee9521454 | ||
|
|
62cef7c2f7 | ||
|
|
1b39f4e47c | ||
|
|
3212b2fcee | ||
|
|
1576541bd1 | ||
|
|
62e97364fb | ||
|
|
93b954ce46 | ||
|
|
1058c8a80e | ||
|
|
7ecb106880 | ||
|
|
b0c1d93c50 | ||
|
|
0c5a039226 | ||
|
|
2677144bfa | ||
|
|
6b30c42c0d | ||
|
|
2f62d29f66 | ||
|
|
828f867a80 | ||
|
|
c9663cf7ab | ||
|
|
b85de51d0c | ||
|
|
dc8e65923d | ||
|
|
1469373b9c | ||
|
|
820e937336 | ||
|
|
8b73878782 | ||
|
|
b15106dac4 | ||
|
|
100316b791 | ||
|
|
3c98425226 | ||
|
|
1aa9c46367 | ||
|
|
5211cab351 | ||
|
|
e89a4cce53 | ||
|
|
cb56b90737 | ||
|
|
761a89f052 | ||
|
|
cab6b56606 | ||
|
|
8a743a91a5 | ||
|
|
5dec5dc392 | ||
|
|
27aa4fca26 | ||
|
|
84a2e50bf7 | ||
|
|
297eb5b25b | ||
|
|
d9d0e1f4ef | ||
|
|
6ec0cc8bbe | ||
|
|
795b77ea3f | ||
|
|
91c97d32bb | ||
|
|
e0c50703d7 | ||
|
|
dab1e76766 | ||
|
|
b8c927df27 | ||
|
|
2684d25f09 | ||
|
|
7c95a77ae2 | ||
|
|
31b115d13b | ||
|
|
d681a6924b | ||
|
|
d0cce84d2e | ||
|
|
532591c34f | ||
|
|
562b9419bd | ||
|
|
7f187224c1 | ||
|
|
79b2121f5e | ||
|
|
0bd477594c | ||
|
|
149cd90fb4 | ||
|
|
f74e719a9f | ||
|
|
8b68beae96 | ||
|
|
9cc0b471f2 | ||
|
|
3e035197d0 | ||
|
|
c9d8a640b6 | ||
|
|
1d97e620fd | ||
|
|
f6299ab5c3 | ||
|
|
eaff4774e0 | ||
|
|
bed52db98a | ||
|
|
bd3ef33607 | ||
|
|
25a594895e | ||
|
|
4d468c8026 | ||
|
|
68ed90487b | ||
|
|
ddc2c67fea | ||
|
|
77459e9c64 | ||
|
|
db64f2f45b | ||
|
|
3f7ea71de7 | ||
|
|
d334418655 | ||
|
|
d1844afeae | ||
|
|
b6dc2bd4d4 | ||
|
|
58906eb3b6 | ||
|
|
50e61604ba | ||
|
|
6bd23f106f | ||
|
|
dfd8348724 | ||
|
|
963a4207e2 | ||
|
|
d2e9352e48 | ||
|
|
a39d91ceb3 | ||
|
|
4e58342677 | ||
|
|
5eeb5a534c | ||
|
|
032836dea4 | ||
|
|
f20c53bced | ||
|
|
a06461eb5e | ||
|
|
34e7d6a617 | ||
|
|
31e44d8c10 | ||
|
|
8fdab4a510 | ||
|
|
385adaa1b0 | ||
|
|
f9506bc9ad | ||
|
|
6b5b517d70 | ||
|
|
80850146c1 | ||
|
|
0680dddab4 | ||
|
|
70aa62c0cf | ||
|
|
06165b3842 | ||
|
|
f21902d60d | ||
|
|
a27f383b62 | ||
|
|
35d4fcc513 | ||
|
|
8a96b00f64 | ||
|
|
2d06fd3d92 | ||
|
|
76050e0722 | ||
|
|
a3f0c2fbf6 | ||
|
|
1ccc4c3746 | ||
|
|
e5d85db888 | ||
|
|
e47b1c3048 | ||
|
|
039791a296 | ||
|
|
e6884b6654 | ||
|
|
8cbae28dd8 | ||
|
|
010a1fecb1 | ||
|
|
96bec55ee7 | ||
|
|
424bb36fc7 | ||
|
|
c24700195f | ||
|
|
f7184b0949 | ||
|
|
999973c349 | ||
|
|
4663ae9e4d | ||
|
|
34fc6af137 | ||
|
|
6cc25a7337 | ||
|
|
48b62f1cab | ||
|
|
5a0ceafa66 | ||
|
|
d31969606e | ||
|
|
d7d170f228 | ||
|
|
cf977befd1 | ||
|
|
1d538b4cb0 | ||
|
|
b42febdf60 | ||
|
|
71915de4b0 | ||
|
|
facb3249d0 | ||
|
|
f323efc61a | ||
|
|
be5f54326e | ||
|
|
65cf042639 | ||
|
|
57526a5df5 | ||
|
|
7d26ff641e | ||
|
|
a8ffe3a6ec | ||
|
|
bda135c435 | ||
|
|
c9b2ac290c | ||
|
|
0fa97aeb56 | ||
|
|
d1422ddc58 | ||
|
|
340d65a47c | ||
|
|
51ffa7e7f7 | ||
|
|
2a89c3721b | ||
|
|
0156bbfc46 | ||
|
|
f89fb76cb2 | ||
|
|
16adb9ee18 | ||
|
|
154a637ee1 | ||
|
|
8b583fcbec | ||
|
|
48dfded132 | ||
|
|
baaa3c4f98 | ||
|
|
838550d78a | ||
|
|
bf8323e79b | ||
|
|
399e4bf654 | ||
|
|
5908b4478e | ||
|
|
e6ab7d825a | ||
|
|
c78e6e8315 | ||
|
|
44571a5af5 | ||
|
|
62274f5129 | ||
|
|
b5b5101faf | ||
|
|
bb232d8f1e | ||
|
|
1b3f5bc8a6 | ||
|
|
1b767f6e5e | ||
|
|
fe647044f8 | ||
|
|
aaca502500 | ||
|
|
7d2695e602 | ||
|
|
69dcfdb2bd | ||
|
|
e179f7e5a4 | ||
|
|
dba3747d98 | ||
|
|
3e7627ac24 | ||
|
|
1b043f77cb | ||
|
|
14cbd4a174 | ||
|
|
cc3353e243 | ||
|
|
683b85c66b | ||
|
|
789710d841 | ||
|
|
1a9ff09f43 | ||
|
|
13b09ffc12 | ||
|
|
83a2a01789 | ||
|
|
e1137c4e1d | ||
|
|
0a6cc0cdf1 | ||
|
|
e96d9112ef | ||
|
|
72aa028bd6 | ||
|
|
f8d5b9bace | ||
|
|
e861e2916f | ||
|
|
23738b109d | ||
|
|
713e11d146 | ||
|
|
444e3154b3 | ||
|
|
270ee60df9 | ||
|
|
1ce041c5e5 | ||
|
|
50940bfd13 | ||
|
|
8cb4151f63 | ||
|
|
7a47b7a85a | ||
|
|
a60a792dfa | ||
|
|
a73a1cc4f5 | ||
|
|
32fbdf8a21 | ||
|
|
75c54bbe3e | ||
|
|
38575039b9 | ||
|
|
43a49b9399 | ||
|
|
618c41ce97 | ||
|
|
bd8c9c354b | ||
|
|
8f5d062a3b | ||
|
|
e1a3950051 | ||
|
|
7e816c1503 | ||
|
|
15d9afb2de | ||
|
|
b5468e5565 | ||
|
|
a4c3a61f13 | ||
|
|
7ce101f7f7 | ||
|
|
b61cab397d | ||
|
|
222e1a51dc | ||
|
|
9fa3464188 | ||
|
|
fee695d3d9 | ||
|
|
140f1d88ea | ||
|
|
c4eeaf2ec8 | ||
|
|
54dd30245f | ||
|
|
263ff63a37 | ||
|
|
f3d16502de | ||
|
|
4b7db298c4 | ||
|
|
65f41452fb | ||
|
|
d7a2ae4b09 | ||
|
|
61ac626542 | ||
|
|
8d7a2f342e | ||
|
|
0db037175f | ||
|
|
98aa463b4e | ||
|
|
0b40d3971d | ||
|
|
a21a769909 | ||
|
|
ca8e540453 | ||
|
|
134950747f | ||
|
|
5abc17adf3 | ||
|
|
f3cd3a402e | ||
|
|
c7217b8475 | ||
|
|
99c5129ea7 | ||
|
|
d025ee0c89 | ||
|
|
c24ddaa190 | ||
|
|
442aa156ac | ||
|
|
e906d41cc2 | ||
|
|
6e09d9e949 | ||
|
|
aea02e212a | ||
|
|
a94a66234a | ||
|
|
4cbbf6972a | ||
|
|
caa9671c96 | ||
|
|
20eb3ed39d | ||
|
|
772a898433 | ||
|
|
b5997d326e | ||
|
|
f8626f81cd | ||
|
|
bc208d2421 | ||
|
|
625ebcc762 | ||
|
|
0a163badf0 | ||
|
|
dea95455a0 | ||
|
|
f0f5988abf | ||
|
|
db5f16985c | ||
|
|
f0105867be | ||
|
|
2c0b314428 | ||
|
|
3cd74f33f1 | ||
|
|
a6ee36f4bc | ||
|
|
9cadf635fb | ||
|
|
43e043b044 | ||
|
|
6bac75d7ab | ||
|
|
7ceb170c5f | ||
|
|
47c7ee71b4 | ||
|
|
4f58452771 | ||
|
|
d5bf15cc36 | ||
|
|
9736541385 | ||
|
|
5591c8316d | ||
|
|
a44d3a2c26 | ||
|
|
dceb300324 | ||
|
|
84b9ca3096 | ||
|
|
17e9f1ea96 | ||
|
|
946ec36840 | ||
|
|
11425a898f | ||
|
|
d0029e7f88 | ||
|
|
bdbdda947f | ||
|
|
f88ab57fa0 | ||
|
|
36442a037e | ||
|
|
5969568dee | ||
|
|
96265ea187 | ||
|
|
9676b1fdca | ||
|
|
556132c2dd | ||
|
|
a22b7b7189 | ||
|
|
8e41a42ced | ||
|
|
e0f7bdfc13 | ||
|
|
ec0b96215f | ||
|
|
e27a8be526 | ||
|
|
70cd9f185e | ||
|
|
33eaf6f86d |
No files matched your search
@@ -4,17 +4,36 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.12.0
|
||||
### User Visible Changes
|
||||
- Added support for ICE market data and order execution
|
||||
### Internal Changes
|
||||
- Lots of permission / admin changes to facilitate ICE
|
||||
|
||||
## 2.11.0
|
||||
### User Visible Changes
|
||||
- Fixed fills form time filter to also apply to SOD fills
|
||||
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
|
||||
- Removed the option to enter manual spread fills
|
||||
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
|
||||
### Internal Changes
|
||||
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
|
||||
- To facilitate support, allowed the bts user to view and edit algos from all other users
|
||||
- Improved system tests
|
||||
- Improved log messages in UI to include dispatcher thread name
|
||||
- Added support for Windows core dumps in the UI
|
||||
|
||||
## 2.10.0
|
||||
### User Visible Changes
|
||||
- Fills now include information about the algo that originated the fill
|
||||
- A new, more powerful PnL form is now available
|
||||
### API Changes
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
### Bug Fixes
|
||||
- Brought back the "select all" check box in the mini order book
|
||||
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
|
||||
### Internal Changes
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
## 2.9.3
|
||||
### Internal Changes
|
||||
- Fixed system test scripts
|
||||
@@ -30,7 +49,7 @@ insert_anchor_links = "right"
|
||||
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
|
||||
### Api
|
||||
- C# API sample now uses synchronous calls.
|
||||
|
||||
|
||||
## 2.9.0
|
||||
### User Visible Changes
|
||||
- Improved Workspace management User Interface
|
||||
@@ -38,11 +57,11 @@ insert_anchor_links = "right"
|
||||
- The Spark UI Window group now has a title in the Windows task bar
|
||||
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
|
||||
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
|
||||
- Added import facilities for GMI position files
|
||||
- Added import facilities for GMI position files
|
||||
- Added the ability for risk managers to enter manual fills
|
||||
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
|
||||
### Bug Fixes
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the method used to determine if a security is expired to take into account the start of day time
|
||||
### API Changes
|
||||
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
|
||||
@@ -79,10 +98,10 @@ insert_anchor_links = "right"
|
||||
- Made it easier to generate simple UI's for new order types
|
||||
### API Changes
|
||||
- Dynamic orders can now use QuantityMeasure parameter types
|
||||
|
||||
|
||||
## 2.7.2
|
||||
### User Visible Changes
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Added new Splitter algo. From the manual: The splitter is useful
|
||||
for pro-rata markets where you want to vary your exposure
|
||||
independent of the order quantity that you want to fill. The
|
||||
@@ -90,13 +109,13 @@ insert_anchor_links = "right"
|
||||
the order quantity but it will put exposed_qty total size on the
|
||||
market. Splitter favors order size over queue position.
|
||||
### Internal Changes
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
## 2.7.1
|
||||
### User Visible Changes
|
||||
- Fixed some issues with the Cover algo, where in some circumstances
|
||||
it would erroneously cancel launched hedges on completion.
|
||||
|
||||
|
||||
## 2.7.0
|
||||
### User Visible Changes
|
||||
- Allowed algos to process "in-flight" fills while shutting down
|
||||
@@ -115,25 +134,25 @@ insert_anchor_links = "right"
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
|
||||
- Fixed a rare race condition that could lead to erroneous limit enforcement
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
|
||||
## 2.6.1
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
|
||||
|
||||
|
||||
## 2.6.0
|
||||
### User Visible Changes
|
||||
- In the ladder made last trade more distinctive when overlayed on the volumn column
|
||||
- In the ladder, corrected display of volume tool tip
|
||||
- In the ladder, corrected display of volume tool tip
|
||||
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
|
||||
- Added an option for the Top Algo to delete itself if within N ticks from top of book
|
||||
- Address a rare issue that could lead to an error report on UI exit
|
||||
- Added margin risk limits
|
||||
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
|
||||
- Avoided switching the current account and order type on open ladders when limits are changed
|
||||
- Avoided switching the current account and order type on open ladders when limits are changed
|
||||
### Internal Changes
|
||||
- Ensure that admin logins are always allowed to login, regardless of keycode status.
|
||||
|
||||
|
||||
## 2.5.0
|
||||
### User Visible Changes
|
||||
- Added loss limit risk check
|
||||
@@ -163,13 +182,13 @@ insert_anchor_links = "right"
|
||||
- Made Cover order more independent from its hedging chidren:
|
||||
- Cover continues to operate even if an hedging child is canceled
|
||||
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed manual fills to appear in day PnL rather than in the SOD records
|
||||
- Add buy/sell coloring to fill rows in fills window
|
||||
- Streamlined the mini order book:
|
||||
- Display of the mini order book popup is quicker
|
||||
- Only exchange orders are displayed
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
|
||||
- A checkbox column has been added to make clearer which rows are selected
|
||||
### API Changes
|
||||
@@ -179,7 +198,7 @@ insert_anchor_links = "right"
|
||||
- Added system requirements to Spark documentation
|
||||
- Implemented keycodes
|
||||
- Temporarily rejected stop and stop limit orders, pending bug fixes
|
||||
|
||||
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
@@ -190,30 +209,30 @@ insert_anchor_links = "right"
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
- Added security symbol column to fills window
|
||||
- Re-organized ladder settings dialog to make it clearer and easier to use
|
||||
|
||||
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
- Corrected handling of stop limit orders
|
||||
|
||||
|
||||
## 2.1.0
|
||||
### User Visible Changes
|
||||
- Added support for the GLBX Iceberg order type
|
||||
@@ -224,7 +243,7 @@ insert_anchor_links = "right"
|
||||
- Added PIQ to the mini order book
|
||||
- Fixed a rare bug that could lead to server failure when requesting market data
|
||||
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
|
||||
|
||||
|
||||
## 2.0.5
|
||||
### User Visible Changes
|
||||
- Fixed a bug in the handling of rejected QHO orders
|
||||
@@ -234,19 +253,19 @@ insert_anchor_links = "right"
|
||||
## 2.0.4
|
||||
### User Visible Changes
|
||||
- Fixed performance regression in order manager
|
||||
|
||||
|
||||
### Internal changes
|
||||
- Improved market data diagnostics for bad configurations
|
||||
- Changed market data default buffer size
|
||||
|
||||
|
||||
## 2.0.3
|
||||
### Internal changes
|
||||
- Improved backwards compatibility in UI settings
|
||||
|
||||
|
||||
|
||||
## 2.0.2
|
||||
### User Visible Changes
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the bid or ask column in the ladder no longer sends an order
|
||||
|
||||
### Internal changes
|
||||
@@ -274,8 +293,8 @@ insert_anchor_links = "right"
|
||||
- Improved settings manager tool output
|
||||
- Added diagnostics in the event MD memory corruption is detected
|
||||
- Added systems tests for join,lean, iceberg and spreader orders
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
## 1.6.2
|
||||
### User Visible Changes
|
||||
### Exchange simulator
|
||||
@@ -314,21 +333,21 @@ insert_anchor_links = "right"
|
||||
- These columns can be restored by toggling their visibility within the PnL's Settings form
|
||||
- A new Traded Today column has been added to the PnL
|
||||
- Filtering on this column will allow a user to restrict the report to only show today's position
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
- Added logging to help troubleshoot shell exit hang
|
||||
|
||||
## 1.5.6
|
||||
### User Visible Changes
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Improved algo documentation
|
||||
- Changed how the spreader uses net change
|
||||
- Simplified trigger algo user interface
|
||||
### Internal changes
|
||||
### Internal changes
|
||||
- Defaulted to not recording sim market data
|
||||
- Improved documentation build
|
||||
- Improved documentation build
|
||||
- Added additional performance tracing
|
||||
|
||||
|
||||
## 1.5.5
|
||||
- Fixed Market View 'stale working orders' bug
|
||||
|
||||
@@ -336,11 +355,11 @@ insert_anchor_links = "right"
|
||||
- Avoided logging when warming up cache
|
||||
|
||||
## 1.5.3
|
||||
- Fixed sounds startup issue
|
||||
- Fixed sounds startup issue
|
||||
|
||||
## 1.5.2
|
||||
- Added better logging to AlgoDefinitionService
|
||||
|
||||
|
||||
## 1.5.1
|
||||
- Fixed missing securities in the Fills and P&L windows.
|
||||
|
||||
@@ -361,7 +380,7 @@ insert_anchor_links = "right"
|
||||
#### Bug Fixes
|
||||
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
|
||||
- Fixed a bug in the order book where sometimes parent orders would not update their last message column.
|
||||
|
||||
|
||||
#### Algos
|
||||
- Added OCO Algo
|
||||
- An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill. Composing this order with Cover simulates scalping functionality
|
||||
@@ -369,7 +388,7 @@ insert_anchor_links = "right"
|
||||
- Improved SDK documentation and usability
|
||||
- Dynamic orders are now properly identified in the order book
|
||||
#### Documentation
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- The documentation has been updated and reorganized to make information easier to find
|
||||
### Internal Changes
|
||||
- Improved robustness of limit system test
|
||||
|
||||
@@ -1,83 +1,74 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using bts.api.core;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
public readonly UserTuple user;
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
IReadOnlyDictionary<uint, string>
|
||||
_account_map = new Dictionary<uint, string>(),
|
||||
_user_map = new Dictionary<uint, string>();
|
||||
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
|
||||
IDisposable _dispose;
|
||||
|
||||
IDisposable _dispose;
|
||||
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import]UserTuple user) {
|
||||
AdminModel(AdminService service, UserTuple user) {
|
||||
this.user = user;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
|
||||
select bts_user;
|
||||
|
||||
IEnumerable<BTSAccountImmutable> active_accounts =
|
||||
from admin_data in update.all
|
||||
from bts_account in admin_data.Accounts
|
||||
where bts_account.IsPresent && _wrapper_subject.Value.can_view_account(bts_account.Name)
|
||||
join account_id in (
|
||||
from bts_user in active_users
|
||||
from account_id in bts_user.UserData.AccountPermissions.AccountPermissions.Keys
|
||||
select account_id
|
||||
) on bts_account.Id equals account_id
|
||||
select bts_account;
|
||||
|
||||
var users = active_users.ToDictionary(u => u.Id, u => u.Name);
|
||||
var accounts = active_accounts.ToDictionary(a => a.Id, a => a.Name);
|
||||
return (users, accounts);
|
||||
}).Subscribe(t => {
|
||||
_account_map = t.accounts;
|
||||
_user_map = t.users;
|
||||
_users_subject.OnNext(_user_map = t.users);
|
||||
_accounts_subject.OnNext(_account_map = t.accounts);
|
||||
});
|
||||
// accounts
|
||||
on_accounts = service.subscribei_admin_data()
|
||||
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
|
||||
usr.UserData.AccountPermissions.AccountPermissions
|
||||
.Where(kv => (kv.Value.TradingPermission != null && kv.Value.TradingPermission.HasPermissions) || kv.Value.AdminPermission.HasPermissions)
|
||||
.Select(kv => ad.Accounts.Where(a => a.Id == kv.Key).TryGetSingle(out var acc) ? acc : (BTSAccountImmutable?)null)
|
||||
.Where(acc => acc.HasValue)
|
||||
.Select(acc => acc.Value)
|
||||
.ToDictionary(acc => acc.Id, acc => acc.Name) :
|
||||
new Dictionary<uint, string>(0))
|
||||
.Replay(1)
|
||||
.RefCount();
|
||||
// users
|
||||
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
|
||||
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
|
||||
service.subscribei_admin_data()
|
||||
.Select(u => u.all.TryFirst())
|
||||
.Where(ad => ad.HasValue)
|
||||
.Select(ad => ad.Value.Users
|
||||
.Where(u => user.can_access_user_data(u.Id))
|
||||
.Select(u => new UserTuple(u.Id, u.Name))
|
||||
.ToDictionary(UserTuple.key_fn))
|
||||
.Table(UserTuple.key_fn)
|
||||
.Cache() :
|
||||
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
|
||||
_dispose = on_accounts.Subscribe(map => _account_map = map);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
|
||||
|
||||
public AccountTuple? get_default_account() {
|
||||
foreach (var kv in _account_map) {
|
||||
return new AccountTuple(kv.Key, kv.Value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public uint? get_user_id(string user) => _id_for_name(user, _user_map);
|
||||
public uint? get_account_id(string account) => _id_for_name(account, _account_map);
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
positions :
|
||||
positions.Filter(on_accounts.Select(map => new Func<T, bool>(p => map.ContainsKey(get_account_id(p)))));
|
||||
|
||||
static uint? _id_for_name(string name, IReadOnlyDictionary<uint, string> map) {
|
||||
foreach (var kv in map) {
|
||||
if (kv.Value == name)
|
||||
return kv.Key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, string> get_account_name) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
positions :
|
||||
positions.Filter(on_accounts.Select(map => {
|
||||
var hash = new HashSet<string>(map.Values);
|
||||
return new Func<T, bool>(v => hash.Contains(get_account_name(v)));
|
||||
}));
|
||||
|
||||
public AccountTuple? get_default_account() =>
|
||||
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,10 @@ using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -23,8 +18,8 @@ namespace bts.ui {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
|
||||
_user = username;
|
||||
public AboutDaemon(IBus bus, UserTuple user) {
|
||||
_user = user.name;
|
||||
_bus = bus;
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
}
|
||||
@@ -63,4 +58,4 @@ namespace bts.ui {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,11 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace bts.spark
|
||||
{
|
||||
namespace bts.spark {
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
public class ErrorReporter : IShellDaemon, IDisposable {
|
||||
readonly ErrorReportService _svc;
|
||||
@@ -21,9 +19,9 @@ namespace bts.spark
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, UserTuple user) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_username = user.name;
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
}
|
||||
|
||||
@@ -48,6 +46,45 @@ namespace bts.spark
|
||||
ErrorMessage = message,
|
||||
});
|
||||
}
|
||||
public async Task report_silently(string contact, string description, string userdata) {
|
||||
var request = append_logs(new ErrorReport {
|
||||
User = _username,
|
||||
ErrorMessage = description,
|
||||
UserDescription = userdata,
|
||||
Contact = contact
|
||||
}.immutable());
|
||||
var reply = await _svc.send_report_async(request);
|
||||
}
|
||||
|
||||
public static ErrorReportImmutable append_logs(ErrorReportImmutable src) {
|
||||
var result = src.CloneToMutable();
|
||||
|
||||
var windows_log = LogUtil.read_windows_event_log();
|
||||
if (!windows_log.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = "Application.evtx.gz",
|
||||
Contents = windows_log
|
||||
});
|
||||
}
|
||||
|
||||
var (data,date) = LogUtil.read_latest_crash_dump();
|
||||
if (!data.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = $"spark-{date.ToString("s")}.dmp.gz",
|
||||
Contents = data
|
||||
});
|
||||
}
|
||||
|
||||
var spark_log = LogUtil.read_logs_compressed();
|
||||
if (!spark_log.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = "log.gz",
|
||||
Contents = spark_log
|
||||
});
|
||||
}
|
||||
return result.immutable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ErrorReporterExtensions {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
@@ -7,15 +8,10 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
using bts.md;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
@@ -25,17 +21,21 @@ namespace bts {
|
||||
readonly Lazy<SyntheticSecurityService> _ss;
|
||||
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
|
||||
|
||||
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
|
||||
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, UserTuple user)
|
||||
: base(mbts.config.Configuration.get<md.RelayOptions>().naggle_ms, MDSourceOptions.DeferInit, user) {
|
||||
_bus = bus;
|
||||
_token = token;
|
||||
_ss = ss;
|
||||
_ss = ss;
|
||||
}
|
||||
|
||||
override public void initialize() {
|
||||
public int priority => 55;
|
||||
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
IBus bus => _bus.Value;
|
||||
|
||||
public override void initialize() {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
@@ -60,16 +60,8 @@ namespace bts {
|
||||
// Post full dictionary of syntheics here
|
||||
bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
_dispose.Add( this.on_securities.Subscribe( (md) => {
|
||||
bus.post(SparkTags.SecuritiesTag, md);
|
||||
}));
|
||||
_dispose.Add(on_securities.Subscribe(md => bus.post(SparkTags.SecuritiesTag, md)));
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
|
||||
public int priority => 55;
|
||||
IBus bus => _bus.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using mbts.ui;
|
||||
using mbts;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.spark {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class OopsDaemon : IShellDaemon, IDisposable {
|
||||
[ImportingConstructor]
|
||||
public OopsDaemon(ErrorReporter e) {
|
||||
var pid = get_pid();
|
||||
if (pid.HasValue) {
|
||||
handle_unclean_shutdown(e, pid.Value);
|
||||
}
|
||||
save_pid();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
clear_pid();
|
||||
}
|
||||
|
||||
void handle_unclean_shutdown(ErrorReporter e, int oldpid) {
|
||||
string extra = "";
|
||||
try {
|
||||
var conf = mbts.config.Configuration.get<bts.config.System>();
|
||||
extra = JsonConvert.SerializeObject(conf, Formatting.Indented);
|
||||
}
|
||||
catch { }
|
||||
Task.Run(() => e.report_silently(
|
||||
$"Host: {Environment.MachineName}",
|
||||
$"pid: {oldpid}\n{extra}",
|
||||
"Automated crash report")
|
||||
);
|
||||
}
|
||||
|
||||
const string _subkey = @"Software\BTS";
|
||||
private void save_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
key.SetValue("pid", System.Diagnostics.Process.GetCurrentProcess().Id, Microsoft.Win32.RegistryValueKind.DWord);
|
||||
}
|
||||
}
|
||||
private void clear_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
key.DeleteValue("pid",false);
|
||||
}
|
||||
}
|
||||
private int? get_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
return key.GetValue("pid") as int?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,11 @@ namespace bts.shell {
|
||||
void _show_order_window(Window window) {
|
||||
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
|
||||
window.Resources[kv.Key] = kv.Value;
|
||||
// the dynamic algo 'SecurityKey' template has a ContractPickerPopup that expects the security map to be available as a DynamicResource.
|
||||
var sub = _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => window.Resources[ResourceKeys.Securities] = sec_map);
|
||||
window.Closed += (s, e) => Disposable.dispose(ref sub);
|
||||
window.Show(-200, -160, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,13 @@ using bts.metrics;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.perf {
|
||||
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class ShellLatencyDaemon : IShellDaemon, IDisposable {
|
||||
readonly ShellLatencyService _service;
|
||||
@@ -24,10 +20,10 @@ namespace bts.perf {
|
||||
Ping _pinger = new Ping();
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
|
||||
public ShellLatencyDaemon(ShellLatencyService service, UserTuple user) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_username = user;
|
||||
_username = user.name;
|
||||
_hostname = Environment.MachineName;
|
||||
|
||||
_forever_ping_host(_pinger);
|
||||
@@ -105,4 +101,4 @@ namespace bts.perf {
|
||||
Disposable.dispose(ref _subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
@@ -40,6 +41,7 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
HamburgerMenuGeometryKey = nameof(HamburgerMenuGeometryKey),
|
||||
HornGeometryKey = nameof(HornGeometryKey),
|
||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
@@ -125,6 +127,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);
|
||||
@@ -172,6 +175,7 @@ namespace mbts.ui {
|
||||
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
|
||||
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
|
||||
public static ImageSource Gear => create_icon(GearGeometryKey);
|
||||
public static ImageSource HamburgerMenu => create_icon(HamburgerMenuGeometryKey);
|
||||
public static ImageSource Horn => create_icon(HornGeometryKey);
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||
|
||||
@@ -13,6 +13,51 @@
|
||||
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>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HamburgerMenuGeometryKey}">
|
||||
M2.384186E-06,24L32.000004,24 32.000004,26 2.384186E-06,26z M0,11.999998L32,11.999998 32,13.999998 0,13.999998z M2.384186E-06,0L32.000004,0 32.000004,2 2.384186E-06,2z
|
||||
</sys:String>
|
||||
|
||||
<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
|
||||
|
||||
@@ -5,7 +5,6 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
@@ -13,13 +12,11 @@ using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using static mbts.ui.shell.ProtoBufSerializationHelper;
|
||||
|
||||
namespace bts.shell
|
||||
{
|
||||
namespace bts.shell {
|
||||
[Export(typeof(IShellModule))]
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
class MarketShellModule : IShellModule {
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace mbts.spark {
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
@@ -54,6 +55,7 @@ namespace mbts.spark {
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
@@ -109,6 +111,7 @@ namespace mbts.spark {
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
@@ -167,15 +170,17 @@ namespace mbts.spark {
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v)) {
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
_settlement.set(md.md.settlement_price);
|
||||
_session_low.set(md.md.session_low);
|
||||
_session_high.set(md.md.session_high);
|
||||
_day_change.set(last_price - settlement);
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace mbts.spark {
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -115,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());
|
||||
//
|
||||
@@ -138,6 +139,7 @@ namespace mbts.spark {
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
@@ -155,16 +157,20 @@ namespace mbts.spark {
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
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)
|
||||
@@ -218,6 +224,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
@@ -250,6 +257,7 @@ namespace mbts.spark {
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace mbts.ui.spark {
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
LastSpd,
|
||||
LastSize,
|
||||
Position,
|
||||
RawDayPosition,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings volume => new GridColumnSettings(col_id(Columns.Volume), DefaultColumnHeaders.Volume, width: DefaultColumnWidths.Volume);
|
||||
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
|
||||
public static GridColumnSettings last_from_spread => new GridColumnSettings(col_id(Columns.LastSpd), DefaultColumnHeaders.S, width: DefaultColumnWidths.Price, is_visible: false);
|
||||
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
|
||||
@@ -138,6 +139,7 @@ namespace bts.shell {
|
||||
yield return security;
|
||||
yield return volume;
|
||||
yield return last;
|
||||
yield return last_from_spread;
|
||||
yield return last_size;
|
||||
yield return position;
|
||||
yield return raw_day_position;
|
||||
@@ -224,6 +226,8 @@ namespace bts.shell {
|
||||
[ProtoMember(25)]
|
||||
public string strip_name;
|
||||
[ProtoMember(26)]
|
||||
public string last_price_from_spread;
|
||||
[ProtoMember(28)]
|
||||
public string mic_code;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.synthetic;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -18,11 +20,36 @@ namespace mbts.spark {
|
||||
readonly uint _user_id;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, AdminModel admin_model, ErrorReporter error, UserTuple user) {
|
||||
_bus = bus;
|
||||
_service = service;
|
||||
_error = error;
|
||||
_user_id = user_id;
|
||||
_user_id = user.id;
|
||||
// synthetic tuples
|
||||
var strategy_source = _bus.subscribe(SparkTags.SyntheticstrategiesTag)
|
||||
.Select(map => MappedDictionary.create<uint, ExecutionStrategyImmutable, (uint security_id, ExecutionStrategyImmutable strategy)>(map, (k, v) => (k, v)))
|
||||
.Table(tup => tup.security_id);
|
||||
|
||||
var synthetic_source = _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Select(map => new FilteredDictionary<uint, SecurityMD>(map, smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic))
|
||||
.Table(smd => smd.security.Id);
|
||||
|
||||
var synthetic_user_join = synthetic_source.LeftJoin(
|
||||
admin_model.all_user_tuples,
|
||||
SecurityMD.key_fn,
|
||||
UserTuple.key_fn,
|
||||
smd => smd.security.Synthetic.UserId,
|
||||
(smd, sid, ut, uid) => new SyntheticTuple(smd.security, ut.name, null),
|
||||
(smd, sid, uid) => new SyntheticTuple(smd.security, null, null),
|
||||
SyntheticTuple.key_fn);
|
||||
|
||||
synthetic_tuples = synthetic_user_join.LeftJoin(
|
||||
strategy_source,
|
||||
SyntheticTuple.key_fn,
|
||||
tup => tup.security_id,
|
||||
(synth_tup, id, strat_tup) => new SyntheticTuple(synth_tup.security, synth_tup.user_name, strat_tup.strategy),
|
||||
(synth_tup, id) => synth_tup,
|
||||
SyntheticTuple.key_fn);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
|
||||
@@ -36,6 +63,8 @@ namespace mbts.spark {
|
||||
.LeftJoin(strategies, (security, strategy) => (new ExecutionStrategyImmutable?(strategy), security), security => (new ExecutionStrategyImmutable?(), security))
|
||||
);
|
||||
|
||||
public IObservable<TableUpdate<SyntheticTuple, uint>> synthetic_tuples { get; }
|
||||
|
||||
public IObservable<ExecutionStrategyImmutable> security_strategy(uint security_key) =>
|
||||
strategy.TrySelect(s => {
|
||||
bool success = s.TryGetValue(security_key, out ExecutionStrategyImmutable result);
|
||||
|
||||
@@ -7,8 +7,15 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for SyntheticManagementView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticManagementView : UserControl {
|
||||
public enum Columns {
|
||||
Name,
|
||||
Description,
|
||||
n_legs,
|
||||
n_working_orders,
|
||||
SecurityKey,
|
||||
User
|
||||
}
|
||||
|
||||
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
|
||||
public SyntheticManagementView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
@@ -27,7 +28,7 @@ namespace mbts.spark {
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
@@ -66,8 +67,8 @@ namespace mbts.spark {
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
current_row_connectable_builder.create(observe_row_strategy),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
|
||||
), SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_observable.Select(s => s.HasValue ? ImmutableHashSet<uint>.Empty.Add(s.Value.Id) : null)), id => id != null
|
||||
), SparkCommands.ShowOrders.complete(() => {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
@@ -75,19 +76,20 @@ namespace mbts.spark {
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var dialog_vm = new ShellSettingsDialogVM<GridSettings, GridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
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.
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
gs_vm.load(s.with(_vm.value.columns.Select(c => c.create())));
|
||||
return gs_vm;
|
||||
});
|
||||
dialog_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -7,6 +8,7 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
@@ -33,13 +35,8 @@ namespace bts.shell {
|
||||
public async void show(SparkTags.Nothing nothing) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.synthetic_management_grid,
|
||||
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
IShellWindow _create(StartupData state) =>
|
||||
new SyntheticManagementWindow(this, _bus, _orders_model, state, _settings_hub.create_synth_settings_manager(state.grid));
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
@@ -67,6 +64,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
|
||||
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, is_visible: false, width: DefaultColumnWidths.User);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
@@ -81,6 +79,7 @@ namespace bts.shell {
|
||||
yield return n_legs;
|
||||
yield return n_working_orders;
|
||||
yield return security_key;
|
||||
yield return user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +100,15 @@ namespace bts.shell {
|
||||
public UIntMinMaxState n_working_orders;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public uint[] user;
|
||||
}
|
||||
|
||||
// migration rules
|
||||
[Export(typeof(ISyntheticManagementMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : ISyntheticManagementMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
@@ -17,43 +20,45 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.SyntheticManagementView;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
|
||||
class SyntheticManagementDialogVM : SecurityGrid<SyntheticManagementDialogVM.Row, SyntheticTuple> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
|
||||
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
|
||||
: base(orders_model.spreader.synthetic_tuples.Select(t => t.state.Values), sorting) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_manager = settings_manager;
|
||||
// init filters
|
||||
user_filter = DiscreteValuesFilterVM.CreateTableUpdate<Row, UserTuple, uint>(r => r.user_tuple, UserTuple.key_fn, out var user_filter_items_observer, out var user_filter_items_view, nameof(Row.user_tuple));
|
||||
user_filter_items_view.CustomSort = Comparer<string>.Default.wrap<string, UserTuple>(ut => ut.name).to_non_generic();
|
||||
if (filters != null) {
|
||||
create_time_filter.load_state(filters.create_time);
|
||||
name_filter.load_state(filters.name);
|
||||
description_filter.load_state(filters.description);
|
||||
n_legs_filter.load_state(filters.n_legs);
|
||||
n_working_orders_filter.load_state(filters.n_working_orders);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
user_filter.load_state(filters.user);
|
||||
}
|
||||
_settings_manager = settings_manager;
|
||||
// columns
|
||||
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
|
||||
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
|
||||
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
new Binding(nameof(Row.show_order_book)),
|
||||
n_working_orders_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
id = col_id(Columns.n_working_orders),
|
||||
Header = DefaultColumnHeaders.N_Working_Orders,
|
||||
Width = DefaultColumnWidths.N_Working_Orders
|
||||
}
|
||||
);
|
||||
});
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user_name, nameof(Row.user_name), user_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
@@ -63,42 +68,43 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
});
|
||||
//
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
grid_settings_manager = new GridSettingsManager(columns);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
|
||||
_resources.AddRange(
|
||||
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
|
||||
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
grid_settings_manager.load_from(s);
|
||||
foreach (var r in row_vms)
|
||||
r.raise_invalidated();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
|
||||
new Row(_bus, row_model.md, row_model.strategy, _orders_model);
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public GridSettingsManager grid_settings_manager { get; }
|
||||
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
public IDataGridColumnPersistentFilter<Row, uint[]> user_filter { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() =>
|
||||
_settings_manager.instance_settings == null ? null : grid_settings_manager.create();
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
protected override Row row_factory(SyntheticTuple row_model) =>
|
||||
new Row(_bus, row_model, _orders_model);
|
||||
|
||||
protected override void on_row_removed(Row row) =>
|
||||
row.Dispose();
|
||||
|
||||
FilterStates _get_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -107,19 +113,19 @@ namespace mbts.spark {
|
||||
name = name_filter.get_state(),
|
||||
n_legs = n_legs_filter.get_state(),
|
||||
n_working_orders = n_working_orders_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
security_key = security_key_filter.get_state(),
|
||||
user = user_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly SyntheticTuple synthetic_tuple;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly Property<uint> _n_working_orders;
|
||||
|
||||
public Row(IBus bus, SecurityMD security_md, ExecutionStrategyImmutable? strategy, OrdersModel orders_model) : base(security_md) {
|
||||
public Row(IBus bus, SyntheticTuple synthetic_tuple, OrdersModel orders_model) : base(synthetic_tuple.security) {
|
||||
_bus = bus;
|
||||
this.strategy = strategy;
|
||||
this.synthetic_tuple = synthetic_tuple;
|
||||
_n_working_orders = backing(nameof(n_working_orders),
|
||||
orders_model.on_security_orders(security_key)
|
||||
.Select(t => (uint)(t.state
|
||||
@@ -133,25 +139,23 @@ namespace mbts.spark {
|
||||
show_order_book = new DelegateCommand(_show_order_book);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public DateTime create_time => DateTime.MinValue; //TODO
|
||||
public string name { get; }
|
||||
public string user_name => synthetic_tuple.user_name;
|
||||
public UserTuple user_tuple => new UserTuple(synthetic_tuple.security.Synthetic.UserId, user_name);
|
||||
public int n_legs { get; }
|
||||
public string description { get; }
|
||||
public ExecutionStrategyImmutable? strategy => synthetic_tuple.strategy;
|
||||
public bool strategy_present => strategy.HasValue;
|
||||
public uint n_working_orders => _n_working_orders.Value;
|
||||
public ICommand show_ladder { get; }
|
||||
public ICommand show_order_book { get; }
|
||||
public ICommand default_command => show_ladder;
|
||||
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
|
||||
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
@@ -163,10 +167,8 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
|
||||
|
||||
void _show_ladder() =>
|
||||
_bus.post(SparkTags.ShowLadderTag, md);
|
||||
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet<uint>.Empty.Add(security.Id));
|
||||
|
||||
void _show_order_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
@@ -177,4 +179,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -62,26 +63,18 @@ namespace mbts.spark {
|
||||
_vm = new AlgoStudioVM(_model);
|
||||
_vm.load_from(_state);
|
||||
_view = new AlgoStudioView { DataContext = _vm };
|
||||
_resources = new CompositeDisposable();
|
||||
_resources = new CompositeDisposable(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.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
|
||||
@@ -114,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() {
|
||||
@@ -146,4 +120,4 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace mbts.spark {
|
||||
_error_reporter = error_reporter;
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace mbts.spark {
|
||||
string _template_key;
|
||||
ICommand _set_value_cmd;
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent)
|
||||
: this(algo, param, parent, (param as ITemplateSelectorKey)?.key) { }
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
|
||||
this.param = param;
|
||||
this.algo = algo;
|
||||
|
||||
+40
@@ -247,6 +247,31 @@
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<!--Note: Ideally, this 'securities BindingProxy' would be a static resource, but it only seems to detect the dynamic securities when it is in the visual tree.-->
|
||||
<!--<Grid.Resources>
|
||||
<u:BindingProxy x:Key="SecuritiesBindingProxy" x:Shared="False" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
|
||||
</Grid.Resources>-->
|
||||
<u:BindingProxy x:Name="SecuritiesBindingProxy" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe>
|
||||
<cv:DictionaryLookupConverter/>
|
||||
<x:Static MemberType="cv:ValueConverters" Member="SecuritySelectorText"/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="Value" ElementName="SecuritiesBindingProxy"/>
|
||||
<Binding Path="param.value"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
@@ -320,6 +345,21 @@
|
||||
</usp:PriceEntryControl>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
|
||||
SelectedSecurityKey="{Binding param.value}"
|
||||
ShowDialogSkirt="True"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityKeyHandler}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
|
||||
@@ -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
|
||||
@@ -118,20 +118,24 @@ namespace mbts.spark {
|
||||
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
|
||||
// all others (easily identified by the enum 'Columns.Foo')
|
||||
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), new TimeFilterVM<FillTuple>(r => r.fill.Timestamp.ToDateTimeOffset().LocalDateTime), DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values.Ordered()), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
|
||||
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
// 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());
|
||||
@@ -144,30 +148,34 @@ 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),
|
||||
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
last_unfiltered_fills = t;
|
||||
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) =>
|
||||
@@ -190,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)
|
||||
@@ -227,6 +235,7 @@ namespace mbts.spark {
|
||||
case Columns.Exchange: return wrap_default(ft => ft.security.exchange_or_default());
|
||||
case Columns.FillKey: return wrap(ft => ft.fill.FillKey);
|
||||
case Columns.FillKind: return wrap_default(ft => ft.fill.FillKind);
|
||||
case Columns.FillStatus: return wrap_default(ft => ft.fill.FillStatus);
|
||||
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
|
||||
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
|
||||
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
|
||||
@@ -279,6 +288,8 @@ 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();
|
||||
@@ -293,6 +304,7 @@ namespace mbts.spark {
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
@@ -306,8 +318,8 @@ namespace mbts.spark {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
|
||||
Price,
|
||||
Size,
|
||||
FillKind,
|
||||
FillStatus,
|
||||
FillKey,
|
||||
OrderKey,
|
||||
SecurityKey
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -37,7 +37,8 @@ namespace bts.shell {
|
||||
{ nameof(FillsVM.Row.side), Columns.Side },
|
||||
{ nameof(FillsVM.Row.size), Columns.Size },
|
||||
{ nameof(FillsVM.Row.time_in_force), Columns.TimeInForce },
|
||||
{ nameof(FillsVM.Row.user), Columns.User }
|
||||
{ nameof(FillsVM.Row.user), Columns.User },
|
||||
{ nameof(FillsVM.Row.fill_status ), Columns.FillStatus },
|
||||
};
|
||||
|
||||
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace bts.shell.fills {
|
||||
[ProtoMember(15)]
|
||||
[DIYGridColumnID(nameof(Columns.Symbol))]
|
||||
public string[] symbol { get; set; }
|
||||
|
||||
[ProtoMember(17)]
|
||||
[DIYGridColumnID(nameof(Columns.FillStatus))]
|
||||
public Fill.Types.FillStatus[] fill_status { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -96,6 +100,7 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
|
||||
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
@@ -17,6 +19,7 @@ using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
|
||||
namespace bts.shell {
|
||||
@@ -50,6 +53,7 @@ namespace bts.shell {
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderAutoLookupTag).Subscribe(_show_windows),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
@@ -212,7 +216,21 @@ namespace bts.shell {
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
var dialog = new ShellSettingsDialog {
|
||||
DataContext = dialog_vm,
|
||||
};
|
||||
dialog.SetBinding(FrameworkElement.MinWidthProperty, new Binding(nameof(Window.SizeToContent)) {
|
||||
Source = dialog,
|
||||
Converter = new WindowMinWidthConverter {
|
||||
SizeToContentThreshold = 657
|
||||
}
|
||||
});
|
||||
dialog.SetBinding(FrameworkElement.MinHeightProperty, new Binding(nameof(Window.SizeToContent)) {
|
||||
Source = dialog,
|
||||
Converter = new WindowMinHeightConverter {
|
||||
SizeToContentThreshold = 331
|
||||
}
|
||||
});
|
||||
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
@@ -273,7 +291,21 @@ namespace bts.shell {
|
||||
});
|
||||
}
|
||||
|
||||
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
|
||||
async void _show_windows(IImmutableSet<uint> securities) {
|
||||
if (securities == null)
|
||||
return;
|
||||
var start_time = DateTime.Now;
|
||||
do {
|
||||
var found = securities.KeyJoin(_securities).ToList();
|
||||
securities = securities.RemoveRange(found.Select(SecurityMD.key_fn));
|
||||
show_windows(found);
|
||||
if (securities.Count == 0)
|
||||
break;
|
||||
await Task.Delay(500);
|
||||
} while ((DateTime.Now - start_time) < _ladder_conf.security_timeout);
|
||||
}
|
||||
|
||||
void show_windows(IEnumerable<SecurityMD> securities) {
|
||||
int n_allowed = (int)_ladder_conf.max_open_ladders - _wm.list_windows().Where(w => w.window_type == _window_type).Count();
|
||||
int n_opened = 0;
|
||||
foreach (var s in securities) {
|
||||
|
||||
@@ -12,6 +12,7 @@ using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -105,12 +106,13 @@ namespace mbts.spark {
|
||||
};
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.execute;
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
|
||||
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
|
||||
_dispose = new CompositeDisposable(
|
||||
mini_ob.Observe().Subscribe(_ => _update.execute()),
|
||||
ObservableAutoWrapper.connect_item_hooks(mini_ob.columns, c => c.Observe().Subscribe(_ => _update.execute())),
|
||||
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
|
||||
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
|
||||
_try_select_desired_account();
|
||||
@@ -130,7 +132,6 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
void dispose_resources() {
|
||||
mini_ob.result_changed -= _update.execute;
|
||||
mini_ob.Dispose();
|
||||
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
|
||||
favorite_algos_vm.Dispose();
|
||||
@@ -163,7 +164,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
public GridSettingsVM mini_ob { get; } = new MiniOrderBookGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
// price format
|
||||
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
|
||||
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
|
||||
@@ -305,7 +306,7 @@ namespace mbts.spark {
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
mini_ob.load_from(settings.mini_ob.value);
|
||||
mini_ob.load(settings.mini_ob.value);
|
||||
// columns
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
@@ -363,14 +364,14 @@ namespace mbts.spark {
|
||||
LadderSettings clone;
|
||||
if (_current == null) {
|
||||
clone = new LadderSettings {
|
||||
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
|
||||
mini_ob = new LadderMiniOrderBookSettings(mini_ob.get_result())
|
||||
};
|
||||
} else {
|
||||
clone = SparkUtils.clone_proto(_current);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
|
||||
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.create());
|
||||
mini_ob.get_result().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.get_result());
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
clone.badge_style = badge_style;
|
||||
@@ -810,4 +811,40 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookGridSettingsVM : GridSettingsVM {
|
||||
public MiniOrderBookGridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null)
|
||||
: base(column_flags) { }
|
||||
|
||||
//public override void sync_params() {
|
||||
// parameters.Clear();
|
||||
// parameters.Add(_font_vm);
|
||||
// parameters.Add(_font_color);
|
||||
// if (_background_enabled)
|
||||
// parameters.Add(_background);
|
||||
// parameters.Add(_selection_highlight);
|
||||
//}
|
||||
}
|
||||
|
||||
//class MiniOrderBookGridSettingsVM : ParameterGroupVM {
|
||||
// public MiniOrderBookGridSettingsVM(GridSettingsVM grid) { }
|
||||
// MiniOrderBookGridSettingsVM(GridSettingsVM grid, ObservableAggregateCollection<object> parameters)
|
||||
// : base("Mini OrderBook", parameters) {
|
||||
// parameters.add_collection(grid.parameters);
|
||||
// }
|
||||
|
||||
// public override void sync_params() {
|
||||
// var fonts_and_colors = HeaderedParameterGroupVM.create("Fonts/Colors", _font_vm, _font_color);
|
||||
// if (_background_enabled)
|
||||
// fonts_and_colors.parameters.Add(_background);
|
||||
// fonts_and_colors.parameters.Add(_selection_highlight);
|
||||
// parameters.SetItems(fonts_and_colors);
|
||||
// }
|
||||
//}
|
||||
|
||||
//class GridSettingsColumnParameterGroupVM : ParameterGroupVM {
|
||||
// public GridSettingsColumnParameterGroupVM(GridSettingsVM grid) : base("Columns") {
|
||||
|
||||
// }
|
||||
//}G
|
||||
}
|
||||
@@ -14,255 +14,177 @@
|
||||
TextElement.FontSize="{x:Static u:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
|
||||
<sys:Double x:Key="VerticalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="CollapsedSizePresetsHeight">24</sys:Double>
|
||||
<!--'Ladder Columns' Column Widths-->
|
||||
<sys:Double x:Key="LadderColumnsShowColumnWidth">57</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsBackgroundColumnWidth">70</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
|
||||
<!--Margins-->
|
||||
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginTPush" Top="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginTPush2" Top="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginIndentChild" Left="{StaticResource IndentChild}"/>
|
||||
<Thickness x:Key="Margin.UP1" Left="{StaticResource HorizontalPush}" Top="{StaticResource VerticalPush}" Right="{StaticResource HorizontalPush}" Bottom="{StaticResource VerticalPush}"/>
|
||||
<!--Colors-->
|
||||
<Color x:Key="LadderColumnDetailsBackgroundColor">LightGray</Color>
|
||||
<Color x:Key="LadderColumnDetailsForegroundColor">Black</Color>
|
||||
<Color x:Key="LadderColumnDetailsBorderColor">Navy</Color>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBackgroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsForegroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBorderBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBorderColor}"/>
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="ColumnSpanToVisibilityConverter" Default="{u:Visibility Visible}">
|
||||
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
|
||||
</cv:ValueMap>
|
||||
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<usp:GridSettingsResources/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
|
||||
<sys:Double x:Key="VerticalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="CollapsedSizePresetsHeight">24</sys:Double>
|
||||
<!--'Ladder Columns' Column Widths-->
|
||||
<sys:Double x:Key="LadderColumnsShowColumnWidth">57</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsBackgroundColumnWidth">70</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
|
||||
<!--Margins-->
|
||||
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginTPush" Top="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginTPush2" Top="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="MarginIndentChild" Left="{StaticResource IndentChild}"/>
|
||||
<Thickness x:Key="Margin.UP1" Left="{StaticResource HorizontalPush}" Top="{StaticResource VerticalPush}" Right="{StaticResource HorizontalPush}" Bottom="{StaticResource VerticalPush}"/>
|
||||
<!--Colors-->
|
||||
<Color x:Key="LadderColumnDetailsBackgroundColor">LightGray</Color>
|
||||
<Color x:Key="LadderColumnDetailsForegroundColor">Black</Color>
|
||||
<Color x:Key="LadderColumnDetailsBorderColor">Navy</Color>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBackgroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsForegroundBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsForegroundColor}"/>
|
||||
<SolidColorBrush x:Key="LadderColumnDetailsBorderBrush" ice:Freeze="True" Color="{StaticResource LadderColumnDetailsBorderColor}"/>
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="ColumnSpanToVisibilityConverter" Default="{u:Visibility Visible}">
|
||||
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
|
||||
</cv:ValueMap>
|
||||
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
|
||||
|
||||
<Style x:Key="ColumnBackground" TargetType="Rectangle">
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
<Style x:Key="VolumeComponentRectangle" TargetType="Rectangle">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Width">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.VolumeBarWidthConverter}">
|
||||
<Binding Path="(usp:LadderSettingsView.VolumeComponentValue)" RelativeSource="{RelativeSource Self}"/>
|
||||
<Binding Path="DataContext.volume.max_total_volume" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="ColumnBackground" TargetType="Rectangle">
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
<Style x:Key="VolumeComponentRectangle" TargetType="Rectangle">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Width">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.VolumeBarWidthConverter}">
|
||||
<Binding Path="(usp:LadderSettingsView.VolumeComponentValue)" RelativeSource="{RelativeSource Self}"/>
|
||||
<Binding Path="DataContext.volume.max_total_volume" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnDetailsHyperlink" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
|
||||
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
|
||||
<Setter Property="Command" Value="{Binding toggle_show_details_cmd}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="Tan"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnDetailsHyperlink" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
|
||||
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
|
||||
<Setter Property="Command" Value="{Binding toggle_show_details_cmd}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="Tan"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnLadderColumnDetailsBorder" TargetType="Border">
|
||||
<Setter Property="Background" Value="{StaticResource LadderColumnDetailsBackgroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="Navy"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="12,10,12,6"/>
|
||||
<Setter Property="Visibility" Value="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnLadderColumnDetailsBorder" TargetType="Border">
|
||||
<Setter Property="Background" Value="{StaticResource LadderColumnDetailsBackgroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="Navy"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="12,10,12,6"/>
|
||||
<Setter Property="Visibility" Value="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HintText" TargetType="TextBlock">
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
<Style x:Key="HintText" TargetType="TextBlock">
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnShowToggle" TargetType="CheckBox">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="IsChecked" Value="{Binding show}"/>
|
||||
<Setter Property="IsEnabled" Value="{Binding can_toggle_show}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.LadderColumnToggleShowToolTipConverter}">
|
||||
<Binding Path="can_toggle_show"/>
|
||||
<Binding Path="show"/>
|
||||
<Binding Path="name"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnShowToggle" TargetType="CheckBox">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="IsChecked" Value="{Binding show}"/>
|
||||
<Setter Property="IsEnabled" Value="{Binding can_toggle_show}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:LadderSettingsView.LadderColumnToggleShowToolTipConverter}">
|
||||
<Binding Path="can_toggle_show"/>
|
||||
<Binding Path="show"/>
|
||||
<Binding Path="name"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnColorPicker" TargetType="c:ColorPickerPopup">
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnBackgroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Palettes" Value="{Binding background_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding background}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnForegroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="3"/>
|
||||
<Setter Property="Palettes" Value="{Binding foreground_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding foreground}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnWidthTickUpDown" TargetType="c:TickUpDownUInt">
|
||||
<Setter Property="Grid.Column" Value="4"/>
|
||||
<Setter Property="Value" Value="{Binding width}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnColorPicker" TargetType="c:ColorPickerPopup">
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnBackgroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Palettes" Value="{Binding background_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding background}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnForegroundPicker" TargetType="c:ColorPickerPopup" BasedOn="{StaticResource LadderColumnColorPicker}">
|
||||
<Setter Property="Grid.Column" Value="3"/>
|
||||
<Setter Property="Palettes" Value="{Binding foreground_palettes}"/>
|
||||
<Setter Property="SelectedColor" Value="{Binding foreground}"/>
|
||||
</Style>
|
||||
<Style x:Key="LadderColumnWidthTickUpDown" TargetType="c:TickUpDownUInt">
|
||||
<Setter Property="Grid.Column" Value="4"/>
|
||||
<Setter Property="Value" Value="{Binding width}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnFontPicker" TargetType="c:PopupControl">
|
||||
<Setter Property="Grid.Column" Value="5"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
<Setter Property="ShowExpanderArrow" Value="False"/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Header" Value="{Binding}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Margin="4,0"
|
||||
Text="{Binding font_display_name}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnSample" TargetType="Border">
|
||||
<Setter Property="Grid.Column" Value="6"/>
|
||||
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="2,0,0,0"/>
|
||||
<Setter Property="Padding" Value="4,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnRowSeparator" TargetType="Rectangle">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="6"/>
|
||||
<Setter Property="Fill" Value="DimGray"/>
|
||||
<Setter Property="Height" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
</Style>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<Style x:Key="LadderColumnFontPicker" TargetType="c:PopupControl">
|
||||
<Setter Property="Grid.Column" Value="5"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="PopupPlacement" Value="Left"/>
|
||||
<Setter Property="ShowExpanderArrow" Value="False"/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Header" Value="{Binding}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Text="{Binding sample_text}"
|
||||
Margin="4,0"
|
||||
Text="{Binding font_display_name}"
|
||||
/>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<!--Orders-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<c:Badge
|
||||
Background="{Binding badge_background}"
|
||||
BorderBrush="{Binding badge_border}"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Width="{x:Static c:Badge.DefaultWidth}"
|
||||
>
|
||||
<c:Badge.TextLines>
|
||||
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
|
||||
<Binding Path="sample_side"/>
|
||||
<Binding Path="sample_quantity"/>
|
||||
<Binding Path="sample_filled"/>
|
||||
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
</MultiBinding>
|
||||
</c:Badge.TextLines>
|
||||
</c:Badge>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Last-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.LastColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnSample" TargetType="Border">
|
||||
<Setter Property="Grid.Column" Value="6"/>
|
||||
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="2,0,0,0"/>
|
||||
<Setter Property="Padding" Value="4,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnRowSeparator" TargetType="Rectangle">
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="6"/>
|
||||
<Setter Property="Fill" Value="DimGray"/>
|
||||
<Setter Property="Height" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
</Style>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
@@ -271,21 +193,10 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
@@ -305,21 +216,11 @@
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
<Border Style="{StaticResource LadderColumnLadderColumnDetailsBorder}">
|
||||
<CheckBox
|
||||
Content="Show Last on Volume Column"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
IsChecked="{Binding volume.show_last_trade}"
|
||||
Visibility="{Binding last_column.show_details, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Volume-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.VolumeColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<!--Orders-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
@@ -331,18 +232,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
@@ -350,114 +240,223 @@
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<Canvas DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="4"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding total.sample_data.total}"
|
||||
Fill="{Binding total.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding total.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding buy.sample_data.buy}"
|
||||
Fill="{Binding buy.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding buy.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding sell.sample_data.sell}"
|
||||
Fill="{Binding sell.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding sell.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="2"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding passive.sample_data.passive}"
|
||||
Fill="{Binding passive.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding passive.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
</Grid>
|
||||
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<TextBlock
|
||||
Grid.ColumnSpan="4"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding passive.sample_data.numbers_text}"
|
||||
Foreground="{Binding general.foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</Canvas>
|
||||
<c:Badge
|
||||
Background="{Binding badge_background}"
|
||||
BorderBrush="{Binding badge_border}"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Width="{x:Static c:Badge.DefaultWidth}"
|
||||
>
|
||||
<c:Badge.TextLines>
|
||||
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
|
||||
<Binding Path="sample_side"/>
|
||||
<Binding Path="sample_quantity"/>
|
||||
<Binding Path="sample_filled"/>
|
||||
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
|
||||
</MultiBinding>
|
||||
</c:Badge.TextLines>
|
||||
</c:Badge>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
<Border Background="{StaticResource LadderColumnDetailsBackgroundBrush}" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Visibility="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="volume_headers" TextElement.Foreground="{StaticResource LadderColumnDetailsForegroundBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show Bar" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Bar Color" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Show Number" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl x:Name="volume_components_items_source" Grid.Row="1" Margin="0,0,0,8" DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" ItemsSource="{Binding volume_components}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" IsEnabled="{Binding show}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="1" Grid.ColumnSpan="5" Background="White" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Margin="0,-1" IsHitTestVisible="False"/>
|
||||
<TextBlock Margin="70,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{StaticResource LadderColumnDetailsForegroundBrush}" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding bars.enabled}"/>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="2"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
PopupPlacement="Left"
|
||||
Palettes="{Binding palettes}"
|
||||
SelectedColor="{Binding bars.value}"
|
||||
/>
|
||||
<CheckBox Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show_numbers}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Last-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.LastColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{Binding font_family}"
|
||||
FontSize="{Binding font_size}"
|
||||
FontWeight="{Binding font_weight}"
|
||||
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Text="{Binding sample_text}"
|
||||
/>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<Border Style="{StaticResource LadderColumnLadderColumnDetailsBorder}">
|
||||
<CheckBox
|
||||
Content="Show Last on Volume Column"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
IsChecked="{Binding volume.show_last_trade}"
|
||||
Visibility="{Binding last_column.show_details, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--Volume-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.VolumeColumnTemplateKey}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsShowColumnWidth}}" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsBackgroundColumnWidth}}" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,2,0">
|
||||
<Hyperlink Style="{StaticResource LadderColumnDetailsHyperlink}">
|
||||
(Advanced)
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<CheckBox Style="{StaticResource LadderColumnShowToggle}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnBackgroundPicker}"/>
|
||||
<c:ColorPickerPopup Style="{StaticResource LadderColumnForegroundPicker}"/>
|
||||
<c:TickUpDownUInt Style="{StaticResource LadderColumnWidthTickUpDown}"/>
|
||||
<c:PopupControl Style="{StaticResource LadderColumnFontPicker}"/>
|
||||
<Border Style="{StaticResource LadderColumnSample}">
|
||||
<Canvas DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="4"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding total.sample_data.total}"
|
||||
Fill="{Binding total.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding total.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding buy.sample_data.buy}"
|
||||
Fill="{Binding buy.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding buy.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="1"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding sell.sample_data.sell}"
|
||||
Fill="{Binding sell.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding sell.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
<Rectangle
|
||||
Grid.Column="2"
|
||||
usp:LadderSettingsView.VolumeComponentValue="{Binding passive.sample_data.passive}"
|
||||
Fill="{Binding passive.bars.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Visibility="{Binding passive.bars.enabled, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource VolumeComponentRectangle}"
|
||||
/>
|
||||
</Grid>
|
||||
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
|
||||
<TextBlock
|
||||
Grid.ColumnSpan="4"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding passive.sample_data.numbers_text}"
|
||||
Foreground="{Binding general.foreground, Converter={StaticResource ColorToBrushConverter}}"
|
||||
/>
|
||||
</Border>
|
||||
</Canvas>
|
||||
</Border>
|
||||
<Rectangle Style="{StaticResource LadderColumnRowSeparator}"/>
|
||||
</Grid>
|
||||
<Border Background="{StaticResource LadderColumnDetailsBackgroundBrush}" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Visibility="{Binding show_details, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="volume_headers" TextElement.Foreground="{StaticResource LadderColumnDetailsForegroundBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show Bar" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Bar Color" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Show Number" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl x:Name="volume_components_items_source" Grid.Row="1" Margin="0,0,0,8" DataContext="{Binding DataContext.volume, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" ItemsSource="{Binding volume_components}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}" IsEnabled="{Binding show}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="1" Grid.ColumnSpan="5" Background="White" BorderBrush="{StaticResource LadderColumnDetailsBorderBrush}" BorderThickness="1" Margin="0,-1" IsHitTestVisible="False"/>
|
||||
<TextBlock Margin="70,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{StaticResource LadderColumnDetailsForegroundBrush}" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding bars.enabled}"/>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="2"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
PopupPlacement="Left"
|
||||
Palettes="{Binding palettes}"
|
||||
SelectedColor="{Binding bars.value}"
|
||||
/>
|
||||
<CheckBox Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show_numbers}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<TabControl>
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Top"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<TabControl BorderThickness="1,1,1,0" ItemContainerStyle="{StaticResource FlatTabItem}" Style="{StaticResource FlatTabControl}">
|
||||
<!-- Ladder Columns Tab -->
|
||||
<TabItem Header="Ladder Columns" VerticalAlignment="Stretch">
|
||||
<Grid Grid.IsSharedSizeScope="True" Margin="8,0">
|
||||
<TabItem Header="Ladder Columns">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -504,7 +503,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Market Break Lines Tab -->
|
||||
<TabItem Header="Market Break Lines" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Price Lines">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
@@ -623,14 +622,14 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Market Price & Size Tab -->
|
||||
<TabItem Header="Market Price and Size Display">
|
||||
<TabItem Header="Price & Size Display">
|
||||
<Grid Margin="6" Grid.IsSharedSizeScope="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
|
||||
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -644,7 +643,7 @@
|
||||
ItemsSource="{Binding format_options}"
|
||||
SelectedItem="{Binding selected_format}"
|
||||
/>
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginIndentChild}"
|
||||
Text="when implied is > 0"
|
||||
@@ -720,8 +719,8 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Size Presets Tab-->
|
||||
<TabItem Header="Order Quantities" VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<TabItem Header="Quantity Buttons">
|
||||
<Grid HorizontalAlignment="Left" Margin="6" VerticalAlignment="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}" MaxHeight="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
@@ -777,7 +776,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Algo Favorites-->
|
||||
<TabItem Header="Algo Favorites" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Algo Favorites">
|
||||
<Grid HorizontalAlignment="Left" Margin="{StaticResource Margin.UP1}" DataContext="{Binding favorite_algos_vm}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -815,7 +814,7 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}">Stay Enabled?</CheckBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
@@ -837,7 +836,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Order Entry Panel Item Control Visbility -->
|
||||
<TabItem Header="Left Panel" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Left Panel">
|
||||
<Grid Margin="6">
|
||||
<Grid.Resources>
|
||||
<sys:Double x:Key="ValueRowHeight">22</sys:Double>
|
||||
@@ -911,7 +910,8 @@
|
||||
<!--Account-->
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Account</TextBlock>
|
||||
<ComboBox Grid.Row="4" Grid.Column="2" ItemsSource="{Binding accounts_view}" SelectedItem="{Binding default_account, Delay=1}"/>
|
||||
<!--Time In Force-->
|
||||
|
||||
<!--Time In Force-->
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" VerticalAlignment="Center">Time In Force</TextBlock>
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" ItemsSource="{Binding time_in_force_options}" SelectedItem="{Binding default_time_in_force, Delay=1}"/>
|
||||
<!--Algo-->
|
||||
@@ -964,30 +964,9 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</GroupBox>
|
||||
<!--
|
||||
<ItemsControl ItemsSource="{Binding order_entry_panel_items}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="52"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding name}"/>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
-->
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Safety Checks" VerticalAlignment="Stretch">
|
||||
<TabItem Header="Safety Checks">
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -1008,8 +987,28 @@
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
<usp:GridSettingsView Height="790" Margin="6" DataContext="{Binding mini_ob}" GridSectionContent="{Binding}" GridSectionContentTemplate="{DynamicResource GridSectionCoreContentTemplate}"/>
|
||||
<TabItem Header="Order Popup">
|
||||
<TabControl BorderThickness="0,1,1,1" Margin="0,8,8,8" TabStripPlacement="Right" Style="{StaticResource FlatTabControl}">
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource FlatTabItem}">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="MinWidth" Value="66"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding mini_ob.parameters}"/>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Header="Columns"
|
||||
Content="{Binding mini_ob}"
|
||||
ContentTemplate="{StaticResource ColumnsTabContent}"
|
||||
Margin="0,-1,0,0"
|
||||
/>
|
||||
</TabControl>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -1,9 +1,13 @@
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
@@ -80,6 +84,7 @@ namespace mbts.spark {
|
||||
_ladder.visible_columns_changed += () => _column_headers_presenter.Columns.SetItems(
|
||||
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
|
||||
this.vm = vm;
|
||||
this.ConnectDataContext<LadderVM>(lvm => lvm.bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => Resources[ResourceKeys.Securities] = sec_map));
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
|
||||
@@ -62,15 +62,15 @@
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
|
||||
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -6,12 +6,6 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for ManualFillRowEditor.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillRowEditor : UserControl {
|
||||
public const string
|
||||
SecuritiesKey = nameof(SecuritiesKey),
|
||||
AccountsKey = nameof(AccountsKey),
|
||||
UsersKey = nameof(UsersKey),
|
||||
SidesKey = nameof(SidesKey);
|
||||
|
||||
public static readonly RoutedCommand
|
||||
CommitCommand = new RoutedCommand(),
|
||||
CancelCommand = new RoutedCommand();
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace bts.shell.manual_fills {
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = m;
|
||||
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
|
||||
RaisePropertyChanged(nameof(security_map));
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
|
||||
|
||||
@@ -50,11 +50,11 @@ namespace mbts.ui.spark {
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
|
||||
// dynamic resources
|
||||
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
|
||||
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
|
||||
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
|
||||
row_editor.Resources[ResourceKeys.Accounts] = vm.accounts;
|
||||
row_editor.Resources[ResourceKeys.Users] = vm.users;
|
||||
row_editor.Resources[ResourceKeys.Sides] = vm.sides;
|
||||
FocusManager.SetIsFocusScope(row_editor, true);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ResourceKeys.Securities] = v);
|
||||
// popup adorner
|
||||
var popup_adorner = new PopupAdorner(
|
||||
data_grid, data_grid,
|
||||
|
||||
@@ -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();
|
||||
@@ -121,7 +122,7 @@ namespace mbts.spark {
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
model.factory.subscribe_users().Select(d => d.Values.Ordered()),
|
||||
ort => ort.order.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
@@ -133,7 +134,7 @@ namespace mbts.spark {
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
model.factory.subscribe_accounts().Select(d => d.Values.Ordered()),
|
||||
ort => ort.order.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -239,13 +241,13 @@ namespace mbts.spark {
|
||||
grid_settings_manager,
|
||||
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
|
||||
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
|
||||
order_mod_vm.algo_vm,
|
||||
order_mod_vm,
|
||||
attach_vm,
|
||||
_show_fills.observe_current(true)
|
||||
.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 => {
|
||||
@@ -735,17 +738,13 @@ namespace mbts.spark {
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions)) { }
|
||||
|
||||
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
_owner = owner;
|
||||
this.algo_vm = algo_vm;
|
||||
algo_vm = AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions);
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
_model.factory.subscribe_account_tuples(),
|
||||
@@ -784,7 +783,8 @@ namespace mbts.spark {
|
||||
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
|
||||
}
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
|
||||
algo_vm);
|
||||
// payup methods
|
||||
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
|
||||
void payup_ask(int ticks) => payup(bbo.ask, ticks);
|
||||
|
||||
@@ -66,6 +66,10 @@ namespace mbts.spark {
|
||||
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
_vm.SubscribeDisposed(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
@@ -172,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
|
||||
};
|
||||
|
||||
|
||||
+37
-48
@@ -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,143 @@
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
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,137 @@
|
||||
<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>
|
||||
<u:BindingProxy x:Name="ActionMenuItemsViewProxy" Value="{DynamicResource ActionMenuItemsView}"/>
|
||||
<c:ActionMenu ItemsSource="{Binding Path='(u:BindingProxy.Value).(CollectionViewSource.View)', ElementName=ActionMenuItemsViewProxy}">
|
||||
<ItemsControl.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
|
||||
</ItemsControl.GroupStyle>
|
||||
</c:ActionMenu>
|
||||
<TabControl MinWidth="500" Margin="0,2,0,0" Style="{StaticResource FlatTabControl}">
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl x:Name="ParameterItemsControl" Grid.IsSharedSizeScope="True">
|
||||
<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,375 +1,379 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.settings;
|
||||
using bts.shell.pnl;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell {
|
||||
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
||||
const string _window_type = "PNL", _window_title = "PNL";
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly PNLConfig _pnl_conf;
|
||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||
|
||||
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
||||
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(PNLSaveState state) =>
|
||||
Task.FromResult(_create(state.settings));
|
||||
|
||||
void _show(PNLSettings settings) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(settings));
|
||||
|
||||
IShellWindow _create(PNLSettings settings) {
|
||||
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
||||
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
||||
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
||||
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
||||
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
||||
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
||||
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
|
||||
var content = new PNLView {
|
||||
DataContext = vm
|
||||
};
|
||||
vm.sorting_changed += sh => {
|
||||
try {
|
||||
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
||||
} catch { }
|
||||
};
|
||||
vm.SubscribeDisposed(
|
||||
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
|
||||
Func<PNLSaveState> get_save_state = () => {
|
||||
var cols = cached_columns.ToArray(c => c.create());
|
||||
var filters = vm.get_filters_states();
|
||||
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
||||
var calc_type = vm.calculation_type;
|
||||
var state = settings_manager.instance_settings;
|
||||
if (state == null) {
|
||||
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||
var defaults = settings_manager.default_settings;
|
||||
if (calc_type == defaults.calculation_type &&
|
||||
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
||||
return new PNLSaveState { use_defaults = true, settings = null };
|
||||
}
|
||||
state = defaults;
|
||||
}
|
||||
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
||||
state = state.with(
|
||||
sorting_criterion,
|
||||
vm.anchors,
|
||||
filters,
|
||||
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
||||
calc_type,
|
||||
vm.header_row_display);
|
||||
return new PNLSaveState { use_defaults = false, settings = state };
|
||||
};
|
||||
IEnumerable<SparkCommand> commands = new[] {
|
||||
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
||||
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
||||
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
settings_manager.instance_settings = get_save_state().settings;
|
||||
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
||||
settings_manager,
|
||||
us => us.pnl,
|
||||
UserSettings.FileExtensions.PNL,
|
||||
s => new PNLSettingsVM(s));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
return dialog;
|
||||
void on_request_close(object sender, EventArgs e) =>
|
||||
WindowUtilities.force_close(dialog);
|
||||
}))
|
||||
};
|
||||
IDisposable dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
vm.load_from(s);
|
||||
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
||||
var fallback_family = new FontFamily(s.grid.font_family);
|
||||
// group
|
||||
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
||||
var group_appearance = s.group_cell_appearance.to_appearance();
|
||||
// header
|
||||
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
||||
var header_appearance = s.header_cell_appearance.to_appearance();
|
||||
//
|
||||
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
||||
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
||||
c.load_from(cs);
|
||||
}
|
||||
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
||||
c.load_group_params(
|
||||
group_appearance.font_size,
|
||||
gcs.font_weight_override ?? group_appearance.font_weight,
|
||||
gcs.font_color_override ?? group_appearance.foreground,
|
||||
group_appearance.font_family);
|
||||
}
|
||||
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
||||
c.load_header_params(
|
||||
header_appearance.font_size,
|
||||
hcs.font_weight_override ?? header_appearance.font_weight,
|
||||
hcs.font_color_override ?? header_appearance.foreground,
|
||||
header_appearance.font_family);
|
||||
}
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
||||
}),
|
||||
content._grid.Columns.Subscribe((s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: add_new(); break;
|
||||
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
remove_old();
|
||||
add_new();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
break;
|
||||
}
|
||||
void add_new() =>
|
||||
cached_columns = e.NewStartingIndex >= 0 ?
|
||||
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
||||
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
||||
|
||||
void remove_old() =>
|
||||
cached_columns = e.OldStartingIndex >= 0 ?
|
||||
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
||||
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
||||
}),
|
||||
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
||||
vm);
|
||||
return (content, commands, get_save_state, dispose);
|
||||
},
|
||||
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
||||
min_height_offset: 60);
|
||||
}
|
||||
|
||||
readonly struct PNLConfig {
|
||||
public readonly bool
|
||||
enable_animations,
|
||||
show_pause_resume;
|
||||
|
||||
public PNLConfig(bts.config.PNLOptions options) {
|
||||
enable_animations = options.enable_animations;
|
||||
show_pause_resume = options.show_pause_resume;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && (
|
||||
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
||||
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
||||
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
||||
//
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
||||
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = actual_trigger_map;
|
||||
foreach (var kv in snapshot) {
|
||||
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
||||
// assume 1 animation per column
|
||||
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
||||
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
||||
actual_col_anims.Difference(default_col_anims,
|
||||
out var exclusive_actual,
|
||||
out var exclusive_defaults,
|
||||
out var diff);
|
||||
var new_col_anims = exclusive_defaults.ToList();
|
||||
if (new_col_anims.Count > 0) {
|
||||
// add the original col animations to the new ones (combine them)
|
||||
new_col_anims.AddRange(actual_col_anims);
|
||||
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var kv in default_trigger_map) {
|
||||
if (!actual_trigger_map.ContainsKey(kv.Key))
|
||||
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
||||
}
|
||||
if (actual_trigger_map != snapshot) {
|
||||
result = actual_trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
var defaults = PNLSettings.Default;
|
||||
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (_apply(columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
||||
var col_list = columns.ToList();
|
||||
int orig_count = col_list.Count;
|
||||
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
||||
if (col_list.Count < orig_count) {
|
||||
result = col_list;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
||||
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
||||
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
||||
if (_apply(triggers, out var new_triggers)) {
|
||||
settings = set_triggers(settings, new_triggers);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = trigger_map;
|
||||
foreach (var t in trigger_map.Values) {
|
||||
var anims = t.animations.ToList();
|
||||
int orig_count = anims.Count;
|
||||
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
||||
if (anims.Count < orig_count)
|
||||
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
||||
}
|
||||
if (trigger_map != snapshot) {
|
||||
result = trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && !settings.grid.enable_background_color) {
|
||||
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.settings;
|
||||
using bts.shell.pnl;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell {
|
||||
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
||||
const string _window_type = "PNL", _window_title = "PNL";
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly PNLConfig _pnl_conf;
|
||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||
|
||||
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
||||
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(PNLSaveState state) =>
|
||||
Task.FromResult(_create(state.settings));
|
||||
|
||||
void _show(PNLSettings settings) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(settings));
|
||||
|
||||
IShellWindow _create(PNLSettings settings) {
|
||||
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
||||
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
||||
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
||||
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
||||
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
||||
SparkWindow<PNLSaveState> main_win = null;
|
||||
main_win = SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
||||
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
|
||||
var content = new PNLView {
|
||||
DataContext = vm
|
||||
};
|
||||
vm.sorting_changed += sh => {
|
||||
try {
|
||||
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
||||
} catch { }
|
||||
};
|
||||
vm.SubscribeDisposed(
|
||||
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
|
||||
Func<PNLSaveState> get_save_state = () => {
|
||||
var cols = cached_columns.ToArray(c => c.create());
|
||||
var filters = vm.get_filters_states();
|
||||
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
||||
var calc_type = vm.calculation_type;
|
||||
var state = settings_manager.instance_settings;
|
||||
if (state == null) {
|
||||
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||
var defaults = settings_manager.default_settings;
|
||||
if (calc_type == defaults.calculation_type &&
|
||||
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
||||
return new PNLSaveState { use_defaults = true, settings = null };
|
||||
}
|
||||
state = defaults;
|
||||
}
|
||||
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
||||
state = state.with(
|
||||
sorting_criterion,
|
||||
vm.anchors,
|
||||
filters,
|
||||
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
||||
calc_type,
|
||||
vm.header_row_display);
|
||||
return new PNLSaveState { use_defaults = false, settings = state };
|
||||
};
|
||||
IEnumerable<SparkCommand> commands = new[] {
|
||||
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
||||
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
||||
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
settings_manager.instance_settings = get_save_state().settings;
|
||||
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
||||
settings_manager,
|
||||
us => us.pnl,
|
||||
UserSettings.FileExtensions.PNL,
|
||||
s => new PNLSettingsVM(s));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
if (main_win != null)
|
||||
dialog_vm.SubscribeDisposed(main_win.literal_title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
return dialog;
|
||||
void on_request_close(object sender, EventArgs e) =>
|
||||
WindowUtilities.force_close(dialog);
|
||||
}))
|
||||
};
|
||||
IDisposable dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
vm.load_from(s);
|
||||
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
||||
var fallback_family = new FontFamily(s.grid.font_family);
|
||||
// group
|
||||
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
||||
var group_appearance = s.group_cell_appearance.to_appearance();
|
||||
// header
|
||||
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
||||
var header_appearance = s.header_cell_appearance.to_appearance();
|
||||
//
|
||||
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
||||
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
||||
c.load_from(cs);
|
||||
}
|
||||
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
||||
c.load_group_params(
|
||||
group_appearance.font_size,
|
||||
gcs.font_weight_override ?? group_appearance.font_weight,
|
||||
gcs.font_color_override ?? group_appearance.foreground,
|
||||
group_appearance.font_family);
|
||||
}
|
||||
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
||||
c.load_header_params(
|
||||
header_appearance.font_size,
|
||||
hcs.font_weight_override ?? header_appearance.font_weight,
|
||||
hcs.font_color_override ?? header_appearance.foreground,
|
||||
header_appearance.font_family);
|
||||
}
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
||||
}),
|
||||
content._grid.Columns.Subscribe((s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: add_new(); break;
|
||||
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
remove_old();
|
||||
add_new();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
break;
|
||||
}
|
||||
void add_new() =>
|
||||
cached_columns = e.NewStartingIndex >= 0 ?
|
||||
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
||||
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
||||
|
||||
void remove_old() =>
|
||||
cached_columns = e.OldStartingIndex >= 0 ?
|
||||
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
||||
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
||||
}),
|
||||
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
||||
vm);
|
||||
return (content, commands, get_save_state, dispose);
|
||||
},
|
||||
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
||||
min_height_offset: 60);
|
||||
return main_win;
|
||||
}
|
||||
|
||||
readonly struct PNLConfig {
|
||||
public readonly bool
|
||||
enable_animations,
|
||||
show_pause_resume;
|
||||
|
||||
public PNLConfig(bts.config.PNLOptions options) {
|
||||
enable_animations = options.enable_animations;
|
||||
show_pause_resume = options.show_pause_resume;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && (
|
||||
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
||||
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
||||
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
||||
//
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
||||
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = actual_trigger_map;
|
||||
foreach (var kv in snapshot) {
|
||||
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
||||
// assume 1 animation per column
|
||||
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
||||
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
||||
actual_col_anims.Difference(default_col_anims,
|
||||
out var exclusive_actual,
|
||||
out var exclusive_defaults,
|
||||
out var diff);
|
||||
var new_col_anims = exclusive_defaults.ToList();
|
||||
if (new_col_anims.Count > 0) {
|
||||
// add the original col animations to the new ones (combine them)
|
||||
new_col_anims.AddRange(actual_col_anims);
|
||||
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var kv in default_trigger_map) {
|
||||
if (!actual_trigger_map.ContainsKey(kv.Key))
|
||||
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
||||
}
|
||||
if (actual_trigger_map != snapshot) {
|
||||
result = actual_trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
var defaults = PNLSettings.Default;
|
||||
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (_apply(columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
||||
var col_list = columns.ToList();
|
||||
int orig_count = col_list.Count;
|
||||
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
||||
if (col_list.Count < orig_count) {
|
||||
result = col_list;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
||||
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
||||
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
||||
if (_apply(triggers, out var new_triggers)) {
|
||||
settings = set_triggers(settings, new_triggers);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = trigger_map;
|
||||
foreach (var t in trigger_map.Values) {
|
||||
var anims = t.animations.ToList();
|
||||
int orig_count = anims.Count;
|
||||
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
||||
if (anims.Count < orig_count)
|
||||
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
||||
}
|
||||
if (trigger_map != snapshot) {
|
||||
result = trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && !settings.grid.enable_background_color) {
|
||||
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using mbts.ui.viewmodels;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLAnchorHierarchyVM : ItemOrderParameterVM<PNLView.Columns> {
|
||||
public PNLAnchorHierarchyVM()
|
||||
: base("", PNLSettings.anchorable_columns) { }
|
||||
}
|
||||
}
|
||||
@@ -1,122 +1,88 @@
|
||||
using bts.proto;
|
||||
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.shell;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.ui.viewmodels;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellAppearanceOverrideVM : ViewModelBase {
|
||||
bool
|
||||
_is_background_overridden,
|
||||
_is_font_color_overridden,
|
||||
_is_font_weight_overridden,
|
||||
_is_font_family_overridden,
|
||||
_is_font_size_overridden;
|
||||
class PNLCellAppearanceOverrideVM : ParameterGroupVM {
|
||||
public PNLCellAppearanceOverrideVM(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;
|
||||
}
|
||||
|
||||
FontWeight _font_weight_override;
|
||||
FontFamily _font_family_override;
|
||||
double _font_size_override = double.NaN;
|
||||
|
||||
// background
|
||||
public bool is_background_overridden { get => _is_background_overridden; set => Set(ref _is_background_overridden, value); }
|
||||
public ColorPickerPopupVM background_override { get; } = new ColorPickerPopupVM {
|
||||
bind_opacity_to_alpha = true,
|
||||
opacity = 1
|
||||
};
|
||||
// font color
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
// font family
|
||||
public bool is_font_family_overriden { get => _is_font_family_overridden; set => Set(ref _is_font_family_overridden, value); }
|
||||
public FontFamily font_family_override { get => _font_family_override; set => Set(ref _font_family_override, value); }
|
||||
// font size
|
||||
public bool is_font_size_overriden { get => _is_font_size_overridden; set => Set(ref _is_font_size_overridden, value); }
|
||||
public double font_size_override { get => _font_size_override; set => Set(ref _font_size_override, value); }
|
||||
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(is_font_weight_overridden ? font_weight_override : (FontWeight?)null),
|
||||
font_color_override: new NullableColorProto(is_font_color_overridden ? font_color_override.color : (Color?)null),
|
||||
background_color_override: new NullableColorProto(is_background_overridden ? background_override.color : (Color?)null));
|
||||
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(GridColumnSettings settings) {
|
||||
if (settings != null)
|
||||
load(settings.font_weight_override, settings.font_color_override, settings.background_color_override);
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
|
||||
Disposable.Create(
|
||||
grid_defaults.subscribe_font().Subscribe(v => {
|
||||
font_weight.family = v.family;
|
||||
font_weight.default_value = v.weight;
|
||||
font_weight.size = v.size;
|
||||
}),
|
||||
grid_defaults.subscribe_font_color().Subscribe(v => {
|
||||
font_color.default_value = v;
|
||||
}),
|
||||
grid_defaults.subscribe_background().Subscribe(v => {
|
||||
background.default_value = v;
|
||||
}));
|
||||
|
||||
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(FontParamsProto font_params) {
|
||||
if (font_params == null)
|
||||
return;
|
||||
public void load(GridColumnSettings value) {
|
||||
// font weight
|
||||
is_font_weight_overridden = font_params.font_weight > 0;
|
||||
if (is_font_weight_overridden)
|
||||
font_weight_override = FontWeight.FromOpenTypeWeight(font_params.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
|
||||
is_font_color_overridden = font_params.font_color > 0;
|
||||
if (is_font_color_overridden)
|
||||
font_color_override.color = font_params.font_color.rgb_color();
|
||||
// font family
|
||||
if (!string.IsNullOrEmpty(font_params.font_family)) {
|
||||
font_family_override = new FontFamily(font_params.font_family);
|
||||
}
|
||||
if (!double.IsNaN(font_params.font_size) && font_params.font_size > 0) {
|
||||
font_size_override = font_params.font_size;
|
||||
} else {
|
||||
font_size_override = double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(PNLAnimation animation) {
|
||||
if (animation != null)
|
||||
load(
|
||||
animation.font_weight.HasValue ? FontWeight.FromOpenTypeWeight(animation.font_weight.Value) : (FontWeight?)null,
|
||||
animation.foreground?.argb_color(),
|
||||
animation.background?.argb_color());
|
||||
}
|
||||
|
||||
public void load(FontWeight? font_weight, Color? font_color, Color? background) {
|
||||
// font weight
|
||||
is_font_weight_overridden = font_weight.HasValue;
|
||||
if (is_font_weight_overridden)
|
||||
font_weight_override = font_weight.Value;
|
||||
// font color
|
||||
is_font_color_overridden = font_color.HasValue;
|
||||
if (is_font_color_overridden)
|
||||
font_color_override.color = font_color.Value;
|
||||
font_color.is_overridden = value.font_color_override.HasValue;
|
||||
if (font_color.is_overridden)
|
||||
font_color.override_value = value.font_color_override.Value;
|
||||
// background
|
||||
is_background_overridden = background.HasValue;
|
||||
if (background.HasValue)
|
||||
background_override.color = background.Value;
|
||||
background.is_overridden = value.background_color_override.HasValue;
|
||||
if (background.is_overridden)
|
||||
background.override_value = value.background_color_override.Value;
|
||||
}
|
||||
|
||||
public PNLAnimation to_animation(PNLView.Columns key) =>
|
||||
new PNLAnimation {
|
||||
column = key,
|
||||
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
|
||||
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
|
||||
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : (int?)null
|
||||
};
|
||||
|
||||
public PNLCellAppearanceProto to_appearance_proto() =>
|
||||
new PNLCellAppearanceProto {
|
||||
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
|
||||
font_params = to_font_params()
|
||||
};
|
||||
|
||||
public FontParamsProto to_font_params() =>
|
||||
new FontParamsProto {
|
||||
typeface = new TypefaceProto(
|
||||
font_family_override?.name(),
|
||||
is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : 0),
|
||||
font_color = is_font_color_overridden ? font_color_override.color.rgb_color() : 0,
|
||||
font_size = font_size_override
|
||||
};
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,51 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System.Windows;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
using bts.collections;
|
||||
using mbts.ui.viewmodels;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellAppearanceVM : ViewModelBase {
|
||||
FontWeight _font_weight;
|
||||
FontFamily _font_family;
|
||||
double _font_size = double.NaN;
|
||||
class PNLHeaderRowCellAppearanceVM : ParameterGroupVM {
|
||||
readonly FontParameterVM _font = new FontParameterVM("Font");
|
||||
readonly ColorParameterVM
|
||||
_background = new ColorParameterVM("Background"),
|
||||
_font_color = new ColorParameterVM("Font Color");
|
||||
readonly BooleanParameterVM
|
||||
_show_totals = new BooleanParameterVM("Show Totals"),
|
||||
_show_selected_totals = new BooleanParameterVM("Show Selected Totals");
|
||||
|
||||
public ColorPickerPopupVM background { get; } = new ColorPickerPopupVM { bind_opacity_to_alpha = true, opacity = 1 };
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
public PNLHeaderRowCellAppearanceVM() : base("Header Row Display") {
|
||||
parameters.AddRange(
|
||||
HeaderedParameterGroupVM.create("Fonts/Colors", _font, _font_color, _background),
|
||||
HeaderedParameterGroupVM.create("Display", _show_totals, _show_selected_totals));
|
||||
}
|
||||
|
||||
public PNLHeaderRowDisplay display {
|
||||
get {
|
||||
var rv = PNLHeaderRowDisplay.None;
|
||||
if (_show_totals.value)
|
||||
rv |= PNLHeaderRowDisplay.Total;
|
||||
if (_show_selected_totals.value)
|
||||
rv |= PNLHeaderRowDisplay.SelectedTotal;
|
||||
return rv;
|
||||
}
|
||||
set {
|
||||
_show_totals.value = (display & PNLHeaderRowDisplay.Total) != 0;
|
||||
_show_selected_totals.value = (display & PNLHeaderRowDisplay.SelectedTotal) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public PNLCellAppearance to_appearance() =>
|
||||
new PNLCellAppearance(font_color.color, background.color, font_weight, font_family, font_size);
|
||||
new PNLCellAppearance(_font_color.value, _background.value, _font.weight, _font.family, _font.size);
|
||||
|
||||
public void load(PNLCellAppearance appearance) {
|
||||
if (appearance.background.HasValue)
|
||||
background.color = appearance.background.Value;
|
||||
_background.value = appearance.background.Value;
|
||||
if (appearance.foreground.HasValue)
|
||||
font_color.color = appearance.foreground.Value;
|
||||
_font_color.value = appearance.foreground.Value;
|
||||
if (appearance.font_family != null)
|
||||
font_family = appearance.font_family;
|
||||
_font.family = appearance.font_family;
|
||||
if (appearance.font_weight.HasValue)
|
||||
font_weight = appearance.font_weight.Value;
|
||||
_font.weight = appearance.font_weight.Value;
|
||||
if (appearance.font_size > 0)
|
||||
font_size = appearance.font_size;
|
||||
_font.size = appearance.font_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +1,216 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Specialized;
|
||||
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;
|
||||
class PNLModularCellAppearanceVM : TabularParameterGroupVM {
|
||||
public static PNLModularCellAppearanceVM create(PNLColumnCellAppearanceVM default_cell_appearance, IReadOnlyObservableCollection<object> rules) {
|
||||
var composite = new ObservableAggregateCollection<object>(new ObservableCollection<object> { default_cell_appearance });
|
||||
composite.add_collection(rules);
|
||||
return new PNLModularCellAppearanceVM(composite) {
|
||||
selected_item = default_cell_appearance
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
PNLModularCellAppearanceVM(ObservableCollection<object> parameters) : base("", parameters) { }
|
||||
}
|
||||
|
||||
class PNLColumnCellAppearanceVM : ParameterGroupVM {
|
||||
PNLCellAppearanceOverrideVM _selected_cell_mode;
|
||||
|
||||
public static Rule create_rule(string rule) =>
|
||||
new Rule(rule);
|
||||
|
||||
public PNLColumnCellAppearanceVM(string display_name) : base(display_name) {
|
||||
parameters.AddRange(default_vm, group_vm, header_vm);
|
||||
_selected_cell_mode = default_vm;
|
||||
}
|
||||
|
||||
public PNLCellAppearanceOverrideVM selected_cell_mode { get => _selected_cell_mode; set => Set(ref _selected_cell_mode, value); }
|
||||
public PNLCellAppearanceOverrideVM default_vm { get; } = new PNLCellAppearanceOverrideVM("Row") { is_expanded = true };
|
||||
public PNLCellAppearanceOverrideVM group_vm { get; } = new PNLCellAppearanceOverrideVM("Group Row");
|
||||
public PNLCellAppearanceOverrideVM header_vm { get; } = new PNLCellAppearanceOverrideVM("Header Row");
|
||||
|
||||
IEnumerable<PNLCellAppearanceOverrideVM> _cell_modes {
|
||||
get {
|
||||
yield return default_vm;
|
||||
yield return group_vm;
|
||||
yield return header_vm;
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
|
||||
new System.Reactive.Disposables.CompositeDisposable(
|
||||
_cell_modes.Select(c => c.connect_default_values(grid_defaults)));
|
||||
|
||||
public class Rule : PNLColumnCellAppearanceVM {
|
||||
public Rule(string rule_text) : base($"When {DisplayNameHelper.expression_snake_to_title_case(rule_text)}") =>
|
||||
this.rule_text = rule_text;
|
||||
|
||||
public string rule_text { get; }
|
||||
}
|
||||
}
|
||||
|
||||
class PNLColumnSettingsVM : GridColumnSettingsVM {
|
||||
readonly ObservableAggregateCollection<object> _cell_appearance_groups = new ObservableAggregateCollection<object>();
|
||||
readonly _AutoRuleTabsWrapperVM _fonts_and_colors;
|
||||
readonly PNLView.Columns _key;
|
||||
|
||||
public PNLColumnSettingsVM(PNLView.Columns key) : base(null) {
|
||||
_key = key;
|
||||
_fonts_and_colors = new _AutoRuleTabsWrapperVM("Fonts/Colors", key);
|
||||
column_layout = new HeaderedParameterGroupVM("Column Layout", new ObservableCollection<object> {
|
||||
header, width, horizontal_alignment
|
||||
});
|
||||
apply_flags(GridColumnSettingsFeatureFlags.None);
|
||||
}
|
||||
|
||||
public HeaderedParameterGroupVM column_layout { get; }
|
||||
public HeaderedParameterGroupVM fonts_and_colors => _fonts_and_colors;
|
||||
public PNLColumnCellAppearanceVM default_cell_appearance => _fonts_and_colors.cell_appearance;
|
||||
|
||||
IEnumerable<PNLCellAppearanceOverrideVM> _all_cell_modes =>
|
||||
_fonts_and_colors.parameters
|
||||
.OfType<PNLColumnCellAppearanceVM>()
|
||||
.SelectMany(c => c.parameters.OfType<PNLCellAppearanceOverrideVM>());
|
||||
|
||||
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);
|
||||
_fonts_and_colors.load_from(value, triggers, group, group_triggers, header_triggers, header);
|
||||
}
|
||||
|
||||
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() =>
|
||||
_fonts_and_colors.create();
|
||||
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) =>
|
||||
_fonts_and_colors.connect_default_values(grid_defaults);
|
||||
|
||||
public override string ToString() => "Default";
|
||||
|
||||
public override void sync_params() =>
|
||||
parameters.SetItems(column_layout, fonts_and_colors);
|
||||
|
||||
/// <summary>
|
||||
/// This class stores a <see cref="PNLColumnCellAppearanceVM"/> instance in a collection of parameters, and automatically replaces it with a <see cref="PNLModularCellAppearanceVM"/> when rules are loaded.
|
||||
/// Conversely, it replaces the <see cref="PNLModularCellAppearanceVM"/> with the original instance when rules are unloaded.
|
||||
/// The impetus: A special 'TabControl' based DataTemplate is defined in PNLSettingsView.xaml as the default for content of type <see cref="PNLModularCellAppearanceVM"/>.
|
||||
/// </summary>
|
||||
class _AutoRuleTabsWrapperVM : HeaderedParameterGroupVM {
|
||||
public readonly PNLColumnCellAppearanceVM cell_appearance;
|
||||
|
||||
readonly ObservableAggregateCollection<object> _composite_parameters;
|
||||
readonly ObservableCollection<PNLColumnCellAppearanceVM.Rule> _rules = new ObservableCollection<PNLColumnCellAppearanceVM.Rule>();
|
||||
readonly IReadOnlyObservableCollection<object> _wrapped_rules;
|
||||
readonly DeferrableActionWrapper _sync_params;
|
||||
PNLModularCellAppearanceVM _cell_appearance_rules;
|
||||
PNLView.Columns _id;
|
||||
|
||||
public _AutoRuleTabsWrapperVM(string display_name, PNLView.Columns id)
|
||||
: this(display_name, id, new ObservableAggregateCollection<object>()) { }
|
||||
|
||||
_AutoRuleTabsWrapperVM(string display_name, PNLView.Columns id, ObservableAggregateCollection<object> parameters) : base(display_name, parameters) {
|
||||
_id = id;
|
||||
_composite_parameters = parameters;
|
||||
cell_appearance = new PNLColumnCellAppearanceVM("Default");
|
||||
_wrapped_rules = _rules.CastObservable<PNLColumnCellAppearanceVM.Rule, object>();
|
||||
//_wrapped_rules = _rules.SelectObservable<PNLColumnCellAppearanceVM, object>(_ => _, obj => (PNLColumnCellAppearanceVM)obj);
|
||||
// If there are no rules present, the view should display a flat list of font/colors parameters.
|
||||
// If there are rules present, the view should use TabItems to represent each rule its associated font/color parameters,
|
||||
// as well as an additional Tab for the 'defaults.'
|
||||
// It is the responsibility of this class, as the viewmodel, to organize the parameters appropriately.
|
||||
// Listen to rules.CollectionChanged events, and toggle between the 'flat list of default parameters' and the TabItem groups when rules.Count > 0 changes.
|
||||
_composite_parameters.add_collection(cell_appearance.parameters);
|
||||
_composite_parameters.add_collection(_wrapped_rules);
|
||||
bool rules_present = false;
|
||||
_sync_params = new DeferrableActionWrapper(sync_params);
|
||||
_rules.CollectionChanged += (s, e) => _sync_params.execute();
|
||||
void sync_params() {
|
||||
if (rules_present != _rules.Count > 0) {
|
||||
rules_present = !rules_present;
|
||||
_composite_parameters.clear_collections();
|
||||
if (rules_present) {
|
||||
if (_cell_appearance_rules == null) {
|
||||
_cell_appearance_rules = PNLModularCellAppearanceVM.create(cell_appearance, _wrapped_rules);
|
||||
}
|
||||
_composite_parameters.insert_collection(0, new ObservableCollection<object> { _cell_appearance_rules });
|
||||
} else {
|
||||
_composite_parameters.insert_collection(0, new ObservableCollection<object> { cell_appearance });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(string rule, PNLAnimation row, PNLAnimation group, PNLAnimation header)> create() =>
|
||||
_rules.Select(r => (r.rule_text, r.default_vm.create(_id), r.group_vm.create(_id), r.header_vm.create(_id)));
|
||||
|
||||
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
|
||||
using (_sync_params.defer_execution()) {
|
||||
_rules.Clear();
|
||||
cell_appearance.default_vm.load(value);
|
||||
cell_appearance.group_vm.load(group);
|
||||
cell_appearance.header_vm.load(header);
|
||||
foreach (var kv in triggers) {
|
||||
var rule_vm = PNLColumnCellAppearanceVM.create_rule(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);
|
||||
_rules.Add(rule_vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridSettingsVM _grid_defaults;
|
||||
|
||||
public IDisposable connect_default_values(GridSettingsVM grid_defaults) {
|
||||
Log.assert(_grid_defaults == null && grid_defaults != null);
|
||||
if (_rules.Count > 0) { }
|
||||
_grid_defaults = grid_defaults;
|
||||
_rules.CollectionChanged += _wtf;
|
||||
_wtf(_rules, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
return Disposable.Create(
|
||||
cell_appearance.connect_default_values(grid_defaults),
|
||||
ObservableAutoWrapper.connect_item_hooks(_rules, r => r.connect_default_values(grid_defaults)),
|
||||
ObservableAutoWrapper.connect_item_hooks(parameters,
|
||||
obj => {
|
||||
if (obj is PNLColumnCellAppearanceVM cell_app) {
|
||||
return cell_app.connect_default_values(grid_defaults);
|
||||
} else if (obj is PNLCellAppearanceOverrideVM cell_app_over) {
|
||||
return cell_app_over.connect_default_values(grid_defaults);
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
private void _wtf(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
if (_rules.Count > 0) {
|
||||
}
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,60 @@
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.viewmodels;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLGroupRowAppearanceVM : ViewModelBase, IValueFactory<(PNLCellAppearance appearance, IReadOnlyList<Color> background_colors)> {
|
||||
FontWeight _font_weight;
|
||||
FontFamily _font_family;
|
||||
double _font_size = double.NaN;
|
||||
class PNLGroupRowCellAppearanceVM : ParameterGroupVM {
|
||||
readonly PNLAlternatingRowBackgroundVM _alternating_background_vm = new PNLAlternatingRowBackgroundVM();
|
||||
readonly ColorParameterVM _font_color = new ColorParameterVM("Font Color");
|
||||
readonly FontParameterVM _font = new FontParameterVM("Font");
|
||||
|
||||
public PNLGroupRowAppearanceVM() {
|
||||
add_background_cmd = new DelegateCommand(_add_background);
|
||||
// 4 elements is enough to alternate through any reasonable number of groups,
|
||||
// and clicking to add new backgrounds seems awkward
|
||||
for (int i = 0; i < 4; i++)
|
||||
_add_background();
|
||||
public PNLGroupRowCellAppearanceVM() : base("Group") {
|
||||
parameters.AddRange(
|
||||
new HeaderedParameterGroupVM("Fonts/Colors", new ObservableCollection<object> { _font, _font_color }),
|
||||
_alternating_background_vm);
|
||||
}
|
||||
|
||||
public ObservableCollection<NamedColorPickerPopupVM> background_colors { get; } = new ObservableCollection<NamedColorPickerPopupVM>();
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
public ICommand add_background_cmd { get; }
|
||||
ObservableCollection<ColorParameterVM> _alternating_backgrounds => _alternating_background_vm.items;
|
||||
|
||||
public (PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) create() =>
|
||||
(new PNLCellAppearance(font_color.color, Colors.Transparent, font_weight, font_family, font_size),
|
||||
background_colors.WrapSelectorRO(cp => cp.color));
|
||||
public (PNLCellAppearance appearance, IReadOnlyList<Color> alternating_backgrounds) create() =>
|
||||
(new PNLCellAppearance(_font_color.value, Colors.Transparent, _font.weight, _font.family, _font.size),
|
||||
_alternating_backgrounds.Cast<ColorParameterVM>().Select(vm => vm.value).ToList());
|
||||
|
||||
public void load_from((PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) value) {
|
||||
var (app, bg_colors) = value;
|
||||
// background colors
|
||||
while (background_colors.Count < bg_colors.Count)
|
||||
_add_background();
|
||||
for (int i = 0; i < bg_colors.Count; i++)
|
||||
background_colors[i].color = bg_colors[i];
|
||||
//
|
||||
if (app.foreground.HasValue) font_color.color = app.foreground.Value;
|
||||
if (app.font_family != null) font_family = app.font_family;
|
||||
if (app.font_weight.HasValue) font_weight = app.font_weight.Value;
|
||||
if (app.font_size > 0) font_size = app.font_size;
|
||||
public void load((PNLCellAppearance appearance, IReadOnlyList<Color> alternating_backgrounds) value) {
|
||||
var (appearance, alternating_backgrounds) = value;
|
||||
for (int i = 0; i < alternating_backgrounds.Count; i++) {
|
||||
if (_alternating_backgrounds.Count > i) {
|
||||
var vm = _alternating_backgrounds[i];
|
||||
vm.value = alternating_backgrounds[i];
|
||||
}
|
||||
}
|
||||
while (alternating_backgrounds.Count > _alternating_backgrounds.Count)
|
||||
_alternating_backgrounds.Add(new ColorParameterVM($"Background {_alternating_backgrounds.Count}") {
|
||||
value = alternating_backgrounds[_alternating_backgrounds.Count]
|
||||
});
|
||||
while (_alternating_backgrounds.Count > alternating_backgrounds.Count)
|
||||
_alternating_backgrounds.RemoveAt(alternating_backgrounds.Count);
|
||||
CollectionHelper.add_or_update_existing(_alternating_backgrounds, alternating_backgrounds, c => new ColorParameterVM("") { value = c }, (vm, c) => ((ColorParameterVM)vm).value = c);
|
||||
if (appearance.foreground.HasValue)
|
||||
_font_color.value = appearance.foreground.Value;
|
||||
if (appearance.font_family != null)
|
||||
_font.family = appearance.font_family;
|
||||
if (appearance.font_weight.HasValue)
|
||||
_font.weight = appearance.font_weight.Value;
|
||||
if (appearance.font_size > 0)
|
||||
_font.size = appearance.font_size;
|
||||
}
|
||||
}
|
||||
|
||||
void _add_background() =>
|
||||
background_colors.Add(new NamedColorPickerPopupVM {
|
||||
bind_opacity_to_alpha = true,
|
||||
name = $"Background {background_colors.Count + 1}"
|
||||
});
|
||||
class PNLAlternatingRowBackgroundVM : HeaderedParameterGroupVM {
|
||||
public PNLAlternatingRowBackgroundVM() : base("Alternating Backgrounds") =>
|
||||
ObservableAutoWrapper.connect(items, parameters, c => c);
|
||||
|
||||
public ObservableCollection<ColorParameterVM> items { get; } = new ObservableCollection<ColorParameterVM>();
|
||||
}
|
||||
}
|
||||
@@ -1,220 +1,145 @@
|
||||
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 mbts;
|
||||
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.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
|
||||
PNLCalculationType _calculation_type;
|
||||
PNLSettings _last_loaded;
|
||||
|
||||
public PNLSettingsVM(PNLSettings settings) {
|
||||
core_settings_vm = new GridSettingsVM();
|
||||
columns = core_settings_vm.columns.CastObservable<GridColumnSettingsVM, PNLColumnSettingsVM>();
|
||||
load(settings);
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(columns, col => col.connect_default_values(core_settings_vm));
|
||||
}
|
||||
|
||||
public PNLAnchorHierarchyVM anchor_hierarcy { get; } = new PNLAnchorHierarchyVM();
|
||||
public GridSettingsVM core_settings_vm { get; }
|
||||
public PNLGroupRowCellAppearanceVM group_row_defaults { get; } = new PNLGroupRowCellAppearanceVM();
|
||||
public PNLHeaderRowCellAppearanceVM header_row_defaults { get; } = new PNLHeaderRowCellAppearanceVM();
|
||||
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 IReadOnlyObservableCollection<PNLColumnSettingsVM> columns { get; }
|
||||
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.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++) {
|
||||
var key = s.grid.columns[i].strong_id<ColumnKeys>();
|
||||
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
|
||||
// anchor index is 1 based, subtract 1 to get 0 based
|
||||
int anchor_index = a.anchor_index.Value - 1;
|
||||
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
|
||||
new_cols.Value.RemoveAt(i);
|
||||
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
|
||||
}
|
||||
int anchor_index = anchor_hierarcy.value.indexOf(key);
|
||||
if (anchor_index != i && anchor_index >= 0) {
|
||||
new_cols.Value.RemoveAt(i);
|
||||
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_hierarcy.value,
|
||||
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.default_cell_appearance.group_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.default_cell_appearance.header_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_defaults.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((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 key = EnumHelper.parse<ColumnKeys>(c.id);
|
||||
var vm = new PNLColumnSettingsVM(key);
|
||||
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;
|
||||
}));
|
||||
}
|
||||
anchor_hierarcy.value = value.anchors.Intersect(PNLSettings.anchorable_columns);
|
||||
header_row_defaults.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,174 @@
|
||||
<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: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:vm="clr-namespace:mbts.ui.viewmodels"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<usp:GridSettingsResources/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<DataTemplate DataType="{x:Type pnl:PNLColumnSettingsVM}">
|
||||
<ItemsControl ItemsSource="{Binding parameters}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type vm:OrderedItemParameterVM}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox x:Name="IncludeToggle" VerticalAlignment="Top" Margin="0,4,0,0" Width="22" IsChecked="{Binding value}">
|
||||
<CheckBox.Height>
|
||||
<Binding Path="ActualHeight" ElementName="AnchorBorder">
|
||||
<Binding.Converter>
|
||||
<!--Offset Margin.Top-->
|
||||
<cv:DoubleSubtractionConverter Value="4"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</CheckBox.Height>
|
||||
</CheckBox>
|
||||
<Border x:Name="AnchorBorder" Grid.Column="1" BorderBrush="Transparent" BorderThickness="0,4,4,4" MinWidth="120" Padding="6,8" Cursor="{StaticResource {x:Static u:CursorsEx.GrabKey}}">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
<u:CheckedStateDefinition Source="{u:DirectReference IncludeToggle}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver"/>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Cursor">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource {x:Static u:CursorsEx.GrabbingKey}}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimation Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Duration="0" To="{x:Static u:BTSColors.header_alternate_color}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="CheckedStates">
|
||||
<VisualState Name="Unchecked"/>
|
||||
<VisualState Name="Checked">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="KeyIcon" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
<DoubleAnimation Storyboard.TargetName="AnchorIndex" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
|
||||
<ColorAnimation Storyboard.TargetName="ColumnNameText" Storyboard.TargetProperty="Foreground.Color" BeginTime="0" Duration="0" To="Gold"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border.Resources>
|
||||
<u:CursorsEx/>
|
||||
</Border.Resources>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Static u:BTSColors.header_color}"/>
|
||||
</Border.Background>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="KeyIcon" Height="11" Opacity="0">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Geometry="{Binding Source={StaticResource {x:Static u:Icons.KeyGeometryKey}}}" Brush="Gold"/>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<TextBlock x:Name="AnchorIndex" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="16,0,0,-2" FontSize="8.5" Foreground="Gold" Opacity="0">
|
||||
<TextBlock.Text>
|
||||
<Binding Path="checked_index">
|
||||
<Binding.Converter>
|
||||
<cv:IntAdditionConverter Value="1"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="ColumnNameText" Grid.Column="1" Margin="6,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold" Text="{Binding}">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{x:Static u:BTSColors.header_text_color}"/>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type pnl:PNLAnchorHierarchyVM}">
|
||||
<Grid Margin="12,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock FontStyle="Italic">
|
||||
<Run>
|
||||
Check the groups you would like to use.
|
||||
</Run>
|
||||
<LineBreak/>
|
||||
<Run>
|
||||
Drag and drop them to change the grouping order.
|
||||
</Run>
|
||||
</TextBlock>
|
||||
<ItemsControl
|
||||
Grid.Row="1"
|
||||
AllowDrop="True"
|
||||
HorizontalAlignment="Left"
|
||||
ItemsSource="{Binding items}"
|
||||
Margin="0,8,0,0"
|
||||
Loaded="_anchor_items_loaded"
|
||||
>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type pnl:PNLSettingsVM}">
|
||||
<Grid>
|
||||
<u:BindingProxy x:Name="ActionMenuItemsViewProxy" Value="{DynamicResource ActionMenuItemsView}"/>
|
||||
<c:ActionMenu ItemsSource="{Binding Path='(u:BindingProxy.Value).(CollectionViewSource.View)', ElementName=ActionMenuItemsViewProxy}">
|
||||
<ItemsControl.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
|
||||
</ItemsControl.GroupStyle>
|
||||
</c:ActionMenu>
|
||||
<TabControl MinWidth="600" Margin="0,2,0,0" Style="{StaticResource FlatTabControl}">
|
||||
<TabItem Header="Grid">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding core_settings_vm.parameters}"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Grouping" Content="{Binding anchor_hierarcy}"/>
|
||||
<TabItem Header="Group Row Display">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding group_row_defaults.parameters}"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Header Row Display">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding header_row_defaults.parameters}"
|
||||
/>
|
||||
</TabItem>
|
||||
<TabItem Header="Columns" Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource ColumnsTabContent}"/>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</UserControl.ContentTemplate>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,129 @@
|
||||
using bts;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for PNLSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class PNLSettingsView : UserControl {
|
||||
public PNLSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
IDisposable _anchor_items_drag_drop_behavior;
|
||||
|
||||
void _anchor_items_loaded(object sender, RoutedEventArgs e) {
|
||||
// connecting this drag/drop behavior is idempotent, just in case Loaded happens twice
|
||||
Disposable.dispose(ref _anchor_items_drag_drop_behavior);
|
||||
if (!(sender is ItemsControl ic))
|
||||
return;
|
||||
var root_grid = Window.GetWindow(ic)?.Content as Grid;
|
||||
bool is_dragging = false;
|
||||
IDisposable drag_op = null;
|
||||
var cursor_resources = new Lazy<CursorsEx>();
|
||||
_anchor_items_drag_drop_behavior = Disposable.Create(() => Disposable.dispose(ref drag_op));
|
||||
DragDropHelper.subscribe_drag_start(ic).Subscribe(pt => {
|
||||
if (!is_dragging &&
|
||||
ic.InputHitTest(pt) is DependencyObject d &&
|
||||
ic.ContainerFromElement(d) is FrameworkElement item_view &&
|
||||
ic.ItemContainerGenerator.ItemFromContainer(item_view) is object item &&
|
||||
item != DependencyProperty.UnsetValue) {
|
||||
//
|
||||
item_view.SetCurrentValue(OpacityProperty, 0.5);
|
||||
is_dragging = true;
|
||||
var sz = item_view.RenderSize;
|
||||
var dv = new DrawingVisual();
|
||||
using (var dc = dv.RenderOpen())
|
||||
dc.DrawRoundedRectangle(new VisualBrush(item_view) { Stretch = Stretch.Uniform }, null, new Rect(sz), 0, 0);
|
||||
SetCurrentValue(CursorProperty, Cursors.ScrollWE);
|
||||
SetCurrentValue(AllowDropProperty, true);
|
||||
is_dragging = true;
|
||||
Disposable.replace(ref drag_op, new CompositeDisposable {
|
||||
MouseTraceAdorner.trace_drag(root_grid, dv, sz),
|
||||
DragDropHelper.connect_vertical_insertion_adorner(ic, root_grid),
|
||||
this.SubscribePreviewDrop(on_drop),
|
||||
this.SubscribePreviewMouseUp(on_preview_mouse_up),
|
||||
this.SubscribePreviewDragOver(on_drag),
|
||||
this.SubscribePreviewDragLeave(on_drag),
|
||||
this.SubscribePreviewDragEnter(on_drag),
|
||||
() => {
|
||||
ClearValue(CursorProperty);
|
||||
ClearValue(AllowDropProperty);
|
||||
item_view.ClearValue(OpacityProperty);
|
||||
is_dragging = false;
|
||||
}});
|
||||
DragDrop.DoDragDrop(this, new DataObject(DataFormats.StringFormat, item), DragDropEffects.Move);
|
||||
}
|
||||
});
|
||||
|
||||
void on_drag(object s, DragEventArgs de) =>
|
||||
de.Effects = DragDropEffects.Move;
|
||||
|
||||
void on_drop(object _, DragEventArgs drop_e) {
|
||||
drop_e.Handled = true;
|
||||
if (!is_dragging)
|
||||
return;
|
||||
Disposable.dispose(ref drag_op);
|
||||
if (drop_e.Data.GetDataPresent(DataFormats.StringFormat) &&
|
||||
drop_e.Data.GetData(DataFormats.StringFormat) is object from &&
|
||||
ic.ItemsSource is IList anchor_items &&
|
||||
anchor_items.Count > 1) {
|
||||
//
|
||||
var pt = drop_e.GetPosition(this);
|
||||
if (!(InputHitTest(pt) is DependencyObject hit))
|
||||
return;
|
||||
try {
|
||||
int from_i = anchor_items.IndexOf(from);
|
||||
// Set the container to the first item who's right edge is left of pt.X (or the last item if none satisfy that condition).
|
||||
// The logic presumes that the items are laid out in a single, horizontal line, from left to right
|
||||
// (not the most elegant way to get the drop target, but it's effective).
|
||||
int to_i = -1;
|
||||
if (!(ic.ContainerFromElement(hit) is FrameworkElement drop_target)) {
|
||||
for (to_i = 0; to_i < ic.Items.Count; to_i++) {
|
||||
if (ic.ItemContainerGenerator.ContainerFromIndex(to_i) is FrameworkElement c) {
|
||||
drop_target = c;
|
||||
var tl = c.TranslatePoint(new Point(0, 0), this);
|
||||
var bottom_edge = tl.Y + c.ActualHeight;
|
||||
if (bottom_edge >= pt.Y)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (drop_target.DataContext is object to) {
|
||||
to_i = anchor_items.IndexOf(to);
|
||||
var drop_target_pt = TranslatePoint(pt, drop_target);
|
||||
if (drop_target_pt.Y > drop_target.ActualHeight / 2) {
|
||||
// target is "Between drop_target and the item below it"
|
||||
if (from_i > to_i)
|
||||
to_i++;
|
||||
} else {
|
||||
// target is "Between drop_target and the item above it"
|
||||
if (from_i < to_i) {
|
||||
to_i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (to_i < 0)
|
||||
return;
|
||||
if (to_i >= anchor_items.Count)
|
||||
to_i = anchor_items.Count - 1;
|
||||
if (from_i >= 0)
|
||||
anchor_items.Move(from_i, to_i);
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"Error dropping anchor hiearchy item in pnl settings dialog: {ex}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_preview_mouse_up(object _, MouseButtonEventArgs __) =>
|
||||
Disposable.dispose(ref drag_op);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -6,8 +7,9 @@ using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly string _template_key;
|
||||
AlgoParamPermissions _permissions;
|
||||
bool _is_enabled, _has_value;
|
||||
T _committed_value, _value;
|
||||
@@ -15,15 +17,16 @@ namespace mbts.spark {
|
||||
public AlgoParamVM(string id, string display_name)
|
||||
: this(id, display_name, default, EqualityComparer<T>.Default) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default) { }
|
||||
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer) {
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
_value = value;
|
||||
_committed_value = value;
|
||||
_comparer = comparer;
|
||||
_template_key = template_key;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
@@ -50,7 +53,9 @@ namespace mbts.spark {
|
||||
public bool is_enabled { get => _is_enabled; set => _set(ref _is_enabled, value); }
|
||||
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
|
||||
public bool is_dirty { get; private set; }
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
public void revert() {
|
||||
if (is_dirty) {
|
||||
|
||||
@@ -67,7 +67,6 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_users() => _admin_model.on_users;
|
||||
public IObservable<IReadOnlyList<UserTuple>> subscribe_user_tuples() => _admin_model.on_user_tuples;
|
||||
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
|
||||
|
||||
#region Defaults
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey:
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: param.AsQuantity = new bts.spark.QuantityMeasure(); break;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Reactive.Linq;
|
||||
namespace mbts.spark {
|
||||
interface IDynamicOrderParamVM {
|
||||
DynamicOrderParamDescriptor descriptor { get; }
|
||||
DynamicOrderParam param { get; set; }
|
||||
DynamicOrderParam param { get; }
|
||||
}
|
||||
|
||||
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
|
||||
@@ -21,26 +21,16 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
|
||||
DynamicOrderParam _param;
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
|
||||
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
|
||||
|
||||
private DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
this.descriptor = descriptor;
|
||||
_param = param;
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
public DynamicOrderParamDescriptor descriptor { get; set; }
|
||||
public DynamicOrderParam param {
|
||||
get => _param;
|
||||
set {
|
||||
_param = value;
|
||||
on_param_changed();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void on_param_changed();
|
||||
public DynamicOrderParam param { get; }
|
||||
}
|
||||
|
||||
class DynamicOrderParamVM<TView, TModel> : DynamicOrderParamVM, IDynamicOrderParamVM<TView> {
|
||||
@@ -53,14 +43,12 @@ namespace mbts.spark {
|
||||
_field_desc = field_descriptor;
|
||||
_convert = convert;
|
||||
_convert_back = convert_back;
|
||||
value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
|
||||
}
|
||||
|
||||
public TView value {
|
||||
get => _value;
|
||||
set {
|
||||
_field_desc.Accessor.SetValue(param, _convert_back(_value = value));
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
set => Set(ref _value, value);
|
||||
}
|
||||
|
||||
public IObservable<TView> subscribe_value() =>
|
||||
@@ -70,11 +58,6 @@ namespace mbts.spark {
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(value);
|
||||
});
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicOrderEnumeratedVM : DynamicOrderParamVM, IDynamicOrderParamVM<string> {
|
||||
@@ -105,11 +88,6 @@ namespace mbts.spark {
|
||||
o.OnNext(selected_element);
|
||||
});
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_sync_elements();
|
||||
RaisePropertyChanged(nameof(selected_element));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
|
||||
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
|
||||
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
|
||||
@@ -148,10 +126,5 @@ namespace mbts.spark {
|
||||
o.OnNext(quantity);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
RaisePropertyChanged(nameof(quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ namespace mbts.ui.spark
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
public DataTemplate SecurityKeyTemplate { get; set; }
|
||||
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
|
||||
@@ -38,6 +39,7 @@ namespace mbts.ui.spark
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DoubleTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return IntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return SecurityKeyTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: return QuantityMeasureTemplate;
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -73,9 +74,9 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<int?, int>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault(), template_key: TemplateKeys.SecurityKey);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity:
|
||||
{
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: {
|
||||
var qm = new DynamicOrderQuantityVM(descriptor);
|
||||
return attached(qm, DynamicOrderHelper.get_quantity_value, DynamicOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
|
||||
}
|
||||
@@ -88,16 +89,16 @@ namespace mbts.spark {
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
|
||||
}
|
||||
|
||||
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default) {
|
||||
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default, string template_key = null) {
|
||||
var fdesc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
|
||||
var pvm = new DynamicOrderParamVM<TView, TModel>(descriptor, fdesc, convert, convert_back);
|
||||
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
|
||||
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value, template_key);
|
||||
|
||||
TView get_from_param(DynamicOrderParam param) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
|
||||
void assign_to_param(DynamicOrderParam param, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
|
||||
}
|
||||
|
||||
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
|
||||
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default, string template_key = null) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
|
||||
add_property<T>(
|
||||
pvm.descriptor.ParamName,
|
||||
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
|
||||
@@ -105,7 +106,8 @@ namespace mbts.spark {
|
||||
() => pvm.value,
|
||||
v => pvm.value = v,
|
||||
pvm.subscribe_value(),
|
||||
default_value);
|
||||
default_value,
|
||||
template_key: template_key);
|
||||
return pvm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace mbts.spark {
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
|
||||
case AlgoType.Composite: vm = composite(key, name, editing_permissions); break;
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
default: vm = generic(key, name, algo_type, editing_permissions); break;
|
||||
//default:
|
||||
// Log.Ui.debug_fatal($"Failed to create {typeof(IAlgoVM)}: unknown algo type '{algo_type}.'");
|
||||
@@ -166,7 +167,8 @@ namespace mbts.spark {
|
||||
};
|
||||
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
|
||||
RaisePropertyChanged(sd.prop_name);
|
||||
Disposable.replace(ref def_sub, new CompositeDisposable());
|
||||
Disposable.dispose(ref def_sub);
|
||||
def_sub = new CompositeDisposable();
|
||||
if (assert(new_algo != null, sd.prop_name, "No algo specified.")) {
|
||||
def_sub.Add(new_algo.subscribe_definition().Subscribe(_ => _def_change_notifier.execute()));
|
||||
if (new_algo is INotifyDataErrorInfo ndei)
|
||||
@@ -255,14 +257,14 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load, Disposable.Create(
|
||||
Disposable.dispose(ref _pending_load);
|
||||
_pending_load = Disposable.Create(
|
||||
// load children
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.SubscribeEach(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
// load regular props
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
|
||||
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom)));
|
||||
}
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
@@ -298,11 +300,12 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load, Disposable.Create(
|
||||
Disposable.dispose(ref _pending_load);
|
||||
_pending_load = Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos))));
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos)));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
// **assume that algos has more than 1 element
|
||||
@@ -542,8 +545,19 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Func<TValue> get_local, Action<TValue> set_local, IObservable<TValue> subscribe_local, TValue non_uniform_value = default, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
|
||||
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local()) { permissions = permissions };
|
||||
protected void add_property<TValue>(
|
||||
string prop_name,
|
||||
Func<TMutable, TValue> get_from_def,
|
||||
Action<TMutable, TValue> assign_to_def,
|
||||
Func<TValue> get_local,
|
||||
Action<TValue> set_local,
|
||||
IObservable<TValue> subscribe_local,
|
||||
TValue non_uniform_value = default,
|
||||
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
|
||||
string template_key = null) {
|
||||
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local(), template_key) {
|
||||
permissions = permissions
|
||||
};
|
||||
_algo_params.Add(param);
|
||||
_auto_props[prop_name] = new AutoProp(prop_name,
|
||||
def => {
|
||||
@@ -816,12 +830,11 @@ namespace mbts.spark {
|
||||
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
|
||||
foreach (var sd in _child_algo_selectors)
|
||||
sd.load_algo_data_recursive(algo_data, default_algo);
|
||||
Disposable.replace(
|
||||
ref _selector_data_hooks,
|
||||
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
|
||||
s.load_algo_data_recursive(algo_data, default_algo);
|
||||
return Disposable.empty;
|
||||
}));
|
||||
Disposable.dispose(ref _selector_data_hooks);
|
||||
_selector_data_hooks = ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
|
||||
s.load_algo_data_recursive(algo_data, default_algo);
|
||||
return Disposable.empty;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,18 +59,26 @@ namespace mbts.spark {
|
||||
this.md_auxiliary_model = md_auxiliary_model;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders =
|
||||
_service
|
||||
.subscribei_orders()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
.join(sstore, order => order.security_key())
|
||||
on_orders = admin_model
|
||||
.permission_filter(
|
||||
_service.subscribei_orders()
|
||||
.Naggle(interval, scheduler),
|
||||
o => o.account_id()
|
||||
).join(sstore, order => order.security_key())
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
|
||||
on_pnl = _position_service.subscribei_pnl_data()
|
||||
.Naggle(interval, scheduler)
|
||||
on_positions = admin_model
|
||||
.permission_filter(
|
||||
_position_service.subscribei_position_data().Naggle(interval, scheduler),
|
||||
p => p.AccountId)
|
||||
.join(sstore, pos => pos.SecurityId)
|
||||
.Table()
|
||||
.Cache();
|
||||
on_pnl = admin_model
|
||||
.permission_filter(
|
||||
_position_service.subscribei_pnl_data().Naggle(interval, scheduler),
|
||||
p => p.Pos.AccountId)
|
||||
.join(sstore, p => p.Pos.SecurityId)
|
||||
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable? sec, PNLUnitImmutable data)>.Default)
|
||||
.Where(t => t.has_changes())
|
||||
@@ -173,10 +181,11 @@ namespace mbts.spark {
|
||||
.Where(t => t.has_changes())
|
||||
.Cache();
|
||||
// fill tuples
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
var on_fills = admin_model
|
||||
.permission_filter(
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler),
|
||||
f => f.Account)
|
||||
.join(sstore, f => f.SecurityKey)
|
||||
.Table().Cache();
|
||||
var fill_order_tuples_join = on_fills.LeftJoin(
|
||||
|
||||
@@ -1,65 +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 mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
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.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;
|
||||
@@ -120,11 +70,11 @@ namespace mbts {
|
||||
OrderKey,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
ParentKey,
|
||||
[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(is_toastable:false)]
|
||||
AttachedTargetOrderKey
|
||||
@@ -363,6 +313,7 @@ namespace mbts {
|
||||
|
||||
[ProtoContract]
|
||||
class OrderRecordTuplePredicateAnimations : IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate> {
|
||||
const bool _default_is_enabled = true;
|
||||
[ProtoMember(1)]
|
||||
OrderRecordTuplePredicate _predicate;
|
||||
[ProtoMember(2)]
|
||||
@@ -371,19 +322,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;
|
||||
}
|
||||
}
|
||||
@@ -41,9 +41,12 @@
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DiscreteValuesFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/TimeFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/ActionMenu/ActionMenu.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/ColorPicker/TabularColorPicker.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style TargetType="ScrollViewer">
|
||||
@@ -559,4 +562,4 @@
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -8,6 +8,7 @@ using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.spark {
|
||||
@@ -41,6 +42,9 @@ namespace bts.spark {
|
||||
SparkCommand.create("P&L (2.0)", Icons.Accounting, SparkTags.ShowPNLTag);
|
||||
|
||||
// Ladder
|
||||
public static PartialCommand<IImmutableSet<uint>> ShowLadderAutoLookup(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderAutoLookupTag, key_gesture);
|
||||
|
||||
public static PartialCommand<SecurityMD> ShowLadder(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderTag, key_gesture);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -55,6 +56,8 @@ namespace mbts.spark {
|
||||
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
|
||||
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
|
||||
public static ITag<SecurityMD> ShowLadderTag { get; } = new SparkTag<SecurityMD>("Show Ladder");
|
||||
// TODO: get rid of the SecurityMD ShowLadderTag. This has created burdensome dependency issues.
|
||||
public static ITag<IImmutableSet<uint>> ShowLadderAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>("Show Ladder Auto Lookup");
|
||||
public static ITag<IReadOnlyList<SecurityMD>> ShowLaddersTag { get; } = new SparkTag<IReadOnlyList<SecurityMD>>(nameof(ShowLaddersTag));
|
||||
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
|
||||
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
|
||||
|
||||
@@ -115,20 +115,29 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
internal class Aggregator<T> : IDisposable, IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
|
||||
// TODO: simplifiy this class to implement IList<IObservableCollection<T>>, or something other than the tuple
|
||||
// it is proving to be more trouble than its worth; the onus should be on callers to wrap/convert their collections if they do not implement IObservableCollection<T>.
|
||||
//internal class Aggregator<T> : IDisposable, IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
|
||||
internal class Aggregator<T> : IDisposable, IReadOnlyList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> {
|
||||
readonly ObservableCollection<T> _target;
|
||||
readonly List<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> _collections = new List<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)>();
|
||||
readonly List<NotifierCollectionWrapper> _collections = new List<NotifierCollectionWrapper>();
|
||||
readonly IList<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> _collection_tuples;
|
||||
bool _is_disposed;
|
||||
|
||||
public Aggregator(ObservableCollection<T> target, params (IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)[] collections) {
|
||||
_collection_tuples = _collections.WrapSelector(nc => nc.tuple, null);
|
||||
_target = target;
|
||||
foreach (var c in collections)
|
||||
Add(c);
|
||||
}
|
||||
|
||||
public (IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) this[int index] {
|
||||
get => _collections[index];
|
||||
set => Insert(index, value);
|
||||
get => _collections[index].tuple;
|
||||
set {
|
||||
// TODO: optimize with 'Replace' ops instead of Remove/Add
|
||||
RemoveAt(index);
|
||||
Insert(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _collections.Count;
|
||||
@@ -138,16 +147,24 @@ namespace mbts.ui.controls {
|
||||
public void Add((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection) {
|
||||
_check_disposed();
|
||||
_target.AddRange(collection.items);
|
||||
_collections.Add(collection);
|
||||
collection.notifier.CollectionChanged += _on_collection_changed;
|
||||
var wrapper = new NotifierCollectionWrapper(collection);
|
||||
_collections.Add(wrapper);
|
||||
wrapper.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public bool Remove((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(collection, out int _, out int collection_index);
|
||||
_remove_collection_items(collection, out int _, out int collection_index, true);
|
||||
_collections.RemoveAt(collection_index);
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
bool _remove(NotifierCollectionWrapper wrapper) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(wrapper, out int _, out int collection_index, true);
|
||||
_collections.RemoveAt(collection_index);
|
||||
collection.notifier.CollectionChanged -= _on_collection_changed;
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
@@ -157,43 +174,47 @@ namespace mbts.ui.controls {
|
||||
if (index == _collections.Count)
|
||||
Add(collection);
|
||||
else {
|
||||
_collections.Insert(index, collection);
|
||||
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
|
||||
collection.notifier.CollectionChanged += _on_collection_changed;
|
||||
var wrapper = new NotifierCollectionWrapper(collection);
|
||||
_collections.Insert(index, wrapper);
|
||||
_on_collection_changed(wrapper, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, wrapper, 0));
|
||||
wrapper.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAt(int index) {
|
||||
_check_disposed();
|
||||
Remove(_collections[index]);
|
||||
_remove(_collections[index]);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
_check_disposed();
|
||||
foreach (var c in _collections)
|
||||
c.notifier.CollectionChanged -= _on_collection_changed;
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
_collections.Clear();
|
||||
_target.Clear();
|
||||
}
|
||||
|
||||
public bool Contains((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) item) =>
|
||||
_collections.Contains(item);
|
||||
_collection_tuples.Contains(item);
|
||||
|
||||
public void CopyTo((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)[] array, int arrayIndex) =>
|
||||
_collections.CopyTo(array, arrayIndex);
|
||||
_collection_tuples.CopyTo(array, arrayIndex);
|
||||
|
||||
public int IndexOf((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) item) =>
|
||||
_collections.IndexOf(item);
|
||||
_collection_tuples.IndexOf(item);
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
foreach (var c in _collections)
|
||||
c.notifier.CollectionChanged -= _on_collection_changed;
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
_is_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> GetEnumerator() => _collections.GetEnumerator();
|
||||
public IEnumerator<(IReadOnlyCollection<T> items, INotifyCollectionChanged notifier)> GetEnumerator() {
|
||||
for (int i = 0; i < _collections.Count; i++)
|
||||
yield return _collections[i].tuple;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -208,40 +229,96 @@ namespace mbts.ui.controls {
|
||||
_target.InsertRange(e.NewStartingIndex + offset, e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
var c = (ObservableCollection<T>)sender;
|
||||
_remove_collection_items((c, c), out int starting_index, out int _);
|
||||
_add_items(c, starting_index);
|
||||
var wrapper = sender as NotifierCollectionWrapper;
|
||||
_remove_collection_items(wrapper, out int starting_index, out int _);
|
||||
_add_items(wrapper.collection, starting_index);
|
||||
foreach (T item in wrapper)
|
||||
_target.Insert(starting_index++, item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _add_items(IList items, int index) {
|
||||
void _add_items(IEnumerable items, int index) {
|
||||
foreach (T item in items)
|
||||
_target.Insert(index++, item);
|
||||
}
|
||||
|
||||
void _remove_collection_items((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection, out int starting_index, out int collection_index) {
|
||||
void _remove_collection_items(NotifierCollectionWrapper wrapper, out int starting_index, out int collection_index, bool disconnect = false) =>
|
||||
_remove_collection_items(wrapper.tuple, out starting_index, out collection_index, disconnect);
|
||||
|
||||
void _remove_collection_items((IReadOnlyCollection<T> items, INotifyCollectionChanged notifier) collection, out int starting_index, out int collection_index, bool disconnect = false) {
|
||||
int ex_count = 0;
|
||||
int i = 0;
|
||||
for (; i < _collections.Count; i++) {
|
||||
if (_collections[i].items == collection.items)
|
||||
if (_collections[i].collection == collection.items) {
|
||||
if (disconnect)
|
||||
_collections[i].CollectionChanged -= _on_collection_changed;
|
||||
break;
|
||||
else ex_count += _collections[i].items.Count;
|
||||
} else ex_count += _collections[i].Count;
|
||||
}
|
||||
starting_index = ex_count;
|
||||
collection_index = i;
|
||||
for (++i; i < _collections.Count; i++)
|
||||
ex_count += _collections[i].items.Count;
|
||||
ex_count += _collections[i].Count;
|
||||
_target.RemoveRange(starting_index, _target.Count - ex_count);
|
||||
}
|
||||
|
||||
int _get_collection_start(object sender) =>
|
||||
_collections.TakeWhile(c => c.items != sender).Sum(c => c.items.Count);
|
||||
_collections.TakeWhile(c => c != sender).Sum(c => c.Count);
|
||||
|
||||
void _check_disposed() {
|
||||
if (_is_disposed)
|
||||
throw new ObjectDisposedException("ObservableCollectionAggregator");
|
||||
}
|
||||
|
||||
class NotifierCollectionWrapper : IReadOnlyCollection<T>, IList, INotifyCollectionChanged {
|
||||
public NotifierCollectionWrapper((IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) tuple)
|
||||
: this(tuple.collection, tuple.notifier) { }
|
||||
|
||||
public NotifierCollectionWrapper(IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) {
|
||||
this.collection = collection;
|
||||
this.notifier = notifier;
|
||||
notifier.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public object this[int index] {
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<T> collection { get; }
|
||||
public INotifyCollectionChanged notifier { get; }
|
||||
|
||||
public (IReadOnlyCollection<T> collection, INotifyCollectionChanged notifier) tuple => (collection, notifier);
|
||||
|
||||
public int Count => collection.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
public bool IsFixedSize => false;
|
||||
|
||||
public object SyncRoot => null;
|
||||
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
public int Add(object value) => throw new NotSupportedException();
|
||||
public void Clear() => throw new NotSupportedException();
|
||||
public bool Contains(object value) => collection.Contains((T)value);
|
||||
public void CopyTo(Array array, int index) {
|
||||
foreach (var v in collection)
|
||||
array.SetValue(v, index++);
|
||||
}
|
||||
public IEnumerator<T> GetEnumerator() => collection.GetEnumerator();
|
||||
public int IndexOf(object value) => collection.indexOf((T)value);
|
||||
public void Insert(int index, object value) => throw new NotSupportedException();
|
||||
public void Remove(object value) => throw new NotSupportedException();
|
||||
public void RemoveAt(int index) => throw new NotSupportedException();
|
||||
IEnumerator IEnumerable.GetEnumerator() => collection.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
namespace mbts {
|
||||
public static class DoubleHelper {
|
||||
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 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);
|
||||
|
||||
@@ -11,6 +11,7 @@ using bts.api.persistence;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reactive.Linq;
|
||||
using bts.spark;
|
||||
using bts;
|
||||
|
||||
namespace mbts.persist
|
||||
{
|
||||
@@ -146,16 +147,18 @@ namespace mbts.persist
|
||||
}
|
||||
|
||||
[Export(typeof(IUserPersist))]
|
||||
public class UserUISettings : UISettingsBase
|
||||
{
|
||||
public class UserUISettings : UISettingsBase {
|
||||
string _user;
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
public UserUISettings(uint user_id, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
_user = $"User {user}";
|
||||
_user = $"User {user_id}";
|
||||
}
|
||||
|
||||
[ImportingConstructor]
|
||||
UserUISettings(UserTuple user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: this(user.id, persist, err) { }
|
||||
|
||||
protected override string user() { return _user; }
|
||||
}
|
||||
}
|
||||
@@ -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,8 +16,10 @@ 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> { }
|
||||
|
||||
static class SettingsMigrationHelper {
|
||||
public static bool apply<T>(T settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, out T new_settings) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts;
|
||||
using bts.settings;
|
||||
using bts.shell.orderbook;
|
||||
using bts.shell.pnl;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
@@ -25,7 +26,11 @@ namespace mbts.persist {
|
||||
static readonly IImmutableProperty<UserSettings, GridSettings>
|
||||
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
|
||||
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
_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));
|
||||
@@ -34,8 +39,10 @@ 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;
|
||||
readonly IUserPersist _persist;
|
||||
|
||||
UserSettings _default_settings;
|
||||
@@ -45,14 +52,18 @@ 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<IPNLSettingsMigrationRule> pnl_migration_rules,
|
||||
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
|
||||
//
|
||||
_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>();
|
||||
init();
|
||||
async void init() {
|
||||
UserSettings s;
|
||||
@@ -62,7 +73,9 @@ namespace mbts.persist {
|
||||
var wrapped_rules = new[] {
|
||||
_fills_migration_rules.merge().wrap(_fills_propi),
|
||||
_mview_migration_rules.merge().wrap(_mview_propi),
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi)
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi),
|
||||
_pnl_migration_rules.merge().wrap(_pnl_propi),
|
||||
_synth_migration_rules.merge().wrap(_synth_propi)
|
||||
};
|
||||
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
|
||||
// TODO: move ladder sanitization logic into ISettingsMigrationRule
|
||||
@@ -124,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,
|
||||
@@ -136,6 +155,12 @@ namespace mbts.persist {
|
||||
_pnl_migration_rules,
|
||||
_pnl_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_synth_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
_synth_migration_rules,
|
||||
_synth_propi);
|
||||
|
||||
public async Task save_default(UserSettings value) {
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
lock (_default_settings_lock) {
|
||||
|
||||
@@ -15,12 +15,8 @@ namespace bts.utils {
|
||||
}
|
||||
|
||||
static class ObjectSnapshot {
|
||||
public static IEnumerable<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() where TSource : class where TValue : class {
|
||||
var tval = typeof(TValue);
|
||||
return typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => tval.IsAssignableFrom(p.PropertyType))
|
||||
.Select(_prop_getter<TSource, TValue>);
|
||||
}
|
||||
public static IReadOnlyList<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() =>
|
||||
PropertyGetterStore<TSource, TValue>.value;
|
||||
|
||||
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
|
||||
_property_table(instance, Array.ConvertAll(prop_names, PropertyInfoStore<T>.type.GetProperty));
|
||||
@@ -162,6 +158,18 @@ namespace bts.utils {
|
||||
}
|
||||
}
|
||||
|
||||
static class PropertyGetterStore<TSource, TValue> {
|
||||
public static readonly IReadOnlyList<Func<TSource, TValue>> value;
|
||||
|
||||
static PropertyGetterStore() {
|
||||
var tval = typeof(TValue);
|
||||
value = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => tval.IsAssignableFrom(p.PropertyType))
|
||||
.Select(_prop_getter<TSource, TValue>)
|
||||
.ToReadOnlyList();
|
||||
}
|
||||
}
|
||||
|
||||
class PropNode {
|
||||
public readonly PropertyInfo prop;
|
||||
|
||||
|
||||
@@ -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,32 @@ 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 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 +79,17 @@ 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 IObservable<T> SubscribeValue<T>(this IProperty<T> property, bool start_immediately) =>
|
||||
start_immediately ?
|
||||
// ensure notifications will be in on the same thread as IProperty<T>.Subscribe (don't use .Select extension)
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(property.Value);
|
||||
return property.Subscribe(ch => o.OnNext(ch.newval));
|
||||
}) :
|
||||
SubscribeValue(property);
|
||||
|
||||
//public static IDisposable S
|
||||
|
||||
public static IProperty<T> block_reentrancy<T>(this IProperty<T> source) =>
|
||||
new GatedPropertyWrapper<T>(source);
|
||||
|
||||
@@ -89,6 +126,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 };
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct SyntheticTuple : IEquatable<SyntheticTuple>, IComparable<SyntheticTuple>, IComparable {
|
||||
public static readonly Func<SyntheticTuple, uint> key_fn = a => a.security.Id;
|
||||
public static readonly Func<SyntheticTuple?, uint> nullable_key_fn = a => a.HasValue ? a.Value.security.Id : 0u;
|
||||
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly string user_name;
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
public readonly int? num_working_orders;
|
||||
|
||||
public SyntheticTuple(SecurityImmutable security, string user_name, ExecutionStrategyImmutable? strategy, int? num_working_orders = null) {
|
||||
this.security = security;
|
||||
this.user_name = user_name;
|
||||
this.strategy = strategy;
|
||||
this.num_working_orders = num_working_orders;
|
||||
}
|
||||
|
||||
public int CompareTo(SyntheticTuple other) =>
|
||||
SecurityHelper.Comparer.Compare(security, other.security);
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is SyntheticTuple other ? CompareTo(other) : 1;
|
||||
|
||||
public bool Equals(SyntheticTuple other) =>
|
||||
security == other.security &&
|
||||
user_name == other.user_name &&
|
||||
EqualityComparer.nullable_equals(strategy, other.strategy);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security.Id.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
user_name == null ? security.ToString() : $"{user_name}:{security}";
|
||||
|
||||
public static bool operator ==(SyntheticTuple a, SyntheticTuple b) => a.Equals(b);
|
||||
public static bool operator !=(SyntheticTuple a, SyntheticTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,23 @@
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
>
|
||||
<!--Control Base Colors-->
|
||||
<Color x:Key="Control.SuperLightGrayColor">#F6F6F6</Color>
|
||||
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
|
||||
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
|
||||
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
|
||||
<Color x:Key="Control.MediumDarkGrayColor">#ACACAC</Color>
|
||||
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
|
||||
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
|
||||
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
|
||||
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
|
||||
<Color x:Key="Control.DarkMediumGrayColor">#FF959595</Color>
|
||||
<Color x:Key="Control.DarkGrayColor">#707070</Color>
|
||||
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
|
||||
|
||||
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
|
||||
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
|
||||
@@ -130,4 +144,4 @@
|
||||
<SolidColorBrush x:Key="ListBoxItem.Selected.Inactive.Border" Color="{StaticResource ListBoxItem.Selected.Inactive.Border.Color}"/>
|
||||
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Background" Color="{StaticResource ListBoxItem.MouseOver.Background.Color}"/>
|
||||
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Border" Color="{StaticResource ListBoxItem.MouseOver.Border.Color}"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -1,78 +1,102 @@
|
||||
<c:PopupControl
|
||||
x:Class="mbts.ui.controls.ActionMenu"
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
Margin="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Padding="2"
|
||||
PopupPlacement="Bottom"
|
||||
ShowExpanderArrow="False"
|
||||
Cursor="Hand"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
>
|
||||
<c:PopupControl.Resources>
|
||||
<CollectionViewSource x:Key="ItemsSourceView" Source="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=c:ActionMenu}}">
|
||||
<CollectionViewSource.GroupDescriptions>
|
||||
<PropertyGroupDescription PropertyName="group"/>
|
||||
</CollectionViewSource.GroupDescriptions>
|
||||
</CollectionViewSource>
|
||||
</c:PopupControl.Resources>
|
||||
<c:PopupControl.Header>
|
||||
<Border Width="16">
|
||||
<SolidColorBrush x:Key="SeparatorFill" Color="{DynamicResource Control.AlphaMediumGrayColor}"/>
|
||||
|
||||
<ControlTemplate x:Key="ActionMenuTemplate" TargetType="c:ActionMenu">
|
||||
<Border
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver" Storyboard="{DynamicResource Button.MouseOver.Storyboard}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.Row="0" Fill="Black" Height="2" HorizontalAlignment="Stretch"/>
|
||||
<Rectangle Grid.Row="1" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
|
||||
<Rectangle Grid.Row="2" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
|
||||
<Button
|
||||
x:Name="button"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Padding="2.5,2"
|
||||
Style="{DynamicResource SparkToolBarButton}"
|
||||
>
|
||||
<Button.Background>
|
||||
<SolidColorBrush/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<Popup c:PopupViewController.ButtonSource="{Binding ElementName=button}" Placement="Bottom" PlacementTarget="{Binding ElementName=button}" StaysOpen="False">
|
||||
<Border Background="{DynamicResource Control.LightGrayBrush}" BorderThickness="1">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource Control.DarkMediumGrayColor}"/>
|
||||
</Border.BorderBrush>
|
||||
<ItemsPresenter/>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
</c:PopupControl.Header>
|
||||
<Menu ItemsSource="{Binding Source={StaticResource ItemsSourceView}}">
|
||||
<Menu.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Menu.ItemsPanel>
|
||||
<Menu.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="GroupItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<StackPanel>
|
||||
<ItemsPresenter/>
|
||||
<Rectangle HorizontalAlignment="Stretch" Height="1" Margin="22,0,0,0" Fill="LightGray"/>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</Menu.GroupStyle>
|
||||
<Menu.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
|
||||
<!--NOTE: CommandParameter binding must be declared BEFORE Command,
|
||||
otherwise, CanExecuted is called before the parameter value is realized...I know, it sucks...-->
|
||||
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Header" Value="{Binding name}"/>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||
<Setter Property="MinWidth" Value="167"/>
|
||||
</Style>
|
||||
</Menu.ItemContainerStyle>
|
||||
</Menu>
|
||||
</c:PopupControl>
|
||||
</ControlTemplate>
|
||||
|
||||
<DataTemplate x:Key="HamburgerButtonTemplate">
|
||||
<Image
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Source="{x:Static u:Icons.HamburgerMenu}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="ActionMenuBase" TargetType="c:ActionMenu">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template" Value="{StaticResource ActionMenuTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type u:MenuItemVM}">
|
||||
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
|
||||
<Style TargetType="c:ActionMenu" BasedOn="{StaticResource ActionMenuBase}">
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
<Setter Property="Margin" Value="1"/>
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource HamburgerButtonTemplate}"/>
|
||||
<Setter Property="Width" Value="22"/>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="MinWidth" Value="167"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="Header" Value="{Binding name}"/>
|
||||
<Setter Property="Icon" Value="{Binding icon, Converter={x:Static cv:ImageSourceIconConverter.Instance}}"/>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,22 +1,15 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for ActionMenu.xaml
|
||||
/// </summary>
|
||||
partial class ActionMenu : PopupControl {
|
||||
public ActionMenu() =>
|
||||
InitializeComponent();
|
||||
class ActionMenu : HeaderedItemsControl {
|
||||
static ActionMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionMenu), new FrameworkPropertyMetadata(typeof(ActionMenu)));
|
||||
|
||||
public static readonly DependencyProperty ItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ActionMenu>(
|
||||
nameof(ItemsSource), Enumerable.Empty<IMenuItemVM>());
|
||||
|
||||
public IEnumerable ItemsSource {
|
||||
get => GetValue(ItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
protected override DependencyObject GetContainerForItemOverride() =>
|
||||
new MenuItem();
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace mbts.ui.controls.adorners {
|
||||
move_to(e.GetPosition(target));
|
||||
|
||||
void move_to(Point pt) =>
|
||||
mta.RenderTransform = new TranslateTransform(pt.X, pt.Y);
|
||||
mta.Margin = new Thickness(pt.X, pt.Y, 0, 0);
|
||||
}
|
||||
|
||||
public static IDisposable trace_drag(FrameworkElement target, Visual trace_visual, Size trace_visual_size) {
|
||||
@@ -55,7 +55,7 @@ namespace mbts.ui.controls.adorners {
|
||||
move_to(e.GetPosition(target));
|
||||
|
||||
void move_to(Point pt) =>
|
||||
mta.RenderTransform = new TranslateTransform(pt.X, pt.Y);
|
||||
mta.Margin = new Thickness(pt.X, pt.Y, 0, 0);
|
||||
}
|
||||
|
||||
protected override Visual GetVisualChild(int index) =>
|
||||
|
||||
@@ -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>
|
||||
<ControlTemplate TargetType="c:ExpanderToggleButton">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -30,7 +31,15 @@
|
||||
Stroke="{StaticResource Path.Static.Brush}"
|
||||
StrokeThickness="2"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
>
|
||||
<Path.Visibility>
|
||||
<Binding Path="ShowExpanderArrow" RelativeSource="{RelativeSource TemplatedParent}">
|
||||
<Binding.Converter>
|
||||
<cv:BooleanToVisibilityConverter VisibilityForFalse="Hidden" VisibilityForTrue="Visible"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Path.Visibility>
|
||||
</Path>
|
||||
<ContentPresenter
|
||||
Grid.Column="1"
|
||||
ContentSource="{TemplateBinding Content}"
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
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)));
|
||||
|
||||
public static readonly DependencyProperty ShowExpanderArrowProperty =
|
||||
WPFHelper.create_dp<bool, ExpanderToggleButton>(nameof(ShowExpanderArrow), true);
|
||||
|
||||
public bool ShowExpanderArrow {
|
||||
get => (bool)GetValue(ShowExpanderArrowProperty);
|
||||
set => SetValue(ShowExpanderArrowProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,126 @@
|
||||
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);
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
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 351 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user