Compare commits

...
232 Commits
Author SHA1 Message Date
Eric Harding ed06924df1 Merged in SPARK-1376 (pull request #42)
SPARK-1176 Named Window

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2018-07-09 21:45:19 +00:00
Eric Harding b78179ccee SPARK-1376 Tweak feature name, allow remove from context menu. 2018-07-09 17:44:28 -04:00
Eric Harding 5a00c5eb2a SPARK-1376 Save closed named windows with workspace 2018-07-09 16:17:48 -04:00
Eric Harding 74fa41f151 Quick cleanup of methods that show up on 'object's intellisense 2018-07-09 16:17:48 -04:00
Eric Harding 32d5fc2c37 SPARK-1376 Bookmarks launcher module 2018-07-09 16:17:48 -04:00
Eric Harding 1049046d0b SPARK-1176 Named Windows 2018-07-09 16:17:48 -04:00
Eric Harding 1f8e0a1e2b SPARK-1446 Try to add securities to security DB even if they already have an assigned security key 2018-07-09 16:09:35 -04:00
Kevin Darby 5d3d5251c7 added git_switch_linux.cmd to switch branches on sparkvm so we don't have to unison linux 2018-07-05 15:07:30 -04:00
Taha Afzal f7cee30511 test for n legs 2018-07-05 10:00:05 -04:00
Taha Afzal 98690ee747 ractional hedging test 2018-07-05 00:04:25 -04:00
Taha Afzal 3e14d83dde Merge branch 'prod' of bitbucket.org:bluetradesys/spark into nlegs 2018-07-03 18:54:56 -04:00
Taha Afzal 746622161c SPARK-1366 unit tests 2018-07-02 20:33:34 -04:00
Taha Afzal c064cdd215 SPARK-1366 code complete, writing tests now 2018-07-02 18:59:51 -04:00
Eric Harding acce1e3f7d SPARK-1376 Need to explicitly ask for direct book maintenance 2018-07-02 11:30:50 -04:00
Taha Afzal 9bdf084838 SPARK-1366 ciruclar buffer for keeping history of drivers complete 2018-07-01 23:40:57 -04:00
Taha Afzal 5132f81bc1 name change to QuoteLegDriver 2018-07-01 20:17:41 -04:00
Taha Afzal ab797a311e QuoterLegDriverData broken into a cpp 2018-07-01 19:48:51 -04:00
Taha Afzal d032b23841 added 'compute_driver' which calculates the driver price/qty from a quoting leg qty - this is now called by both n legs and 2 legs 2018-07-01 18:33:04 -04:00
Taha Afzal 8c68a66ef1 Merge branch 'prod' of bitbucket.org:bluetradesys/spark into nlegs 2018-06-29 19:14:56 -04:00
Taha Afzal d617d1597c SPARK 1432 fractional legs 2018-06-29 18:58:44 -04:00
Pedro Pinto 625f76e1f4 Made iceberg order type more robust.
We were asserting that completed orders must have non zero fills, but
its easy for an algo to break that so we handle it.
2018-06-28 15:14:52 -04:00
Eric Harding ccff3ee160 SPARK-1427 monitor dispatcher background thread on sparkd 2018-06-28 11:24:35 -04:00
Eric Harding 1b1c8a7c1d Allow for discontiguous keys in replay md 2018-06-27 11:25:46 -04:00
Pedro Pinto 5206a378ff Fixed stop order price encoding 2018-06-27 11:12:34 -04:00
Pedro Pinto 7d2047d7ce Fixed the shared data problem that was crashing O'Grady. 2018-06-27 11:12:34 -04:00
Taha Afzal 8a43a4d157 SPARK-1416 (reactivated terminal status) 2018-06-27 11:12:34 -04:00
Jake Norbut 230e9e525f SPARK-1409 - slow picker fixed? 2018-06-27 11:12:34 -04:00
Jake Norbut 034318db5e fix early orders unsub - exception was taking out the observable 2018-06-27 11:12:34 -04:00
Pedro Pinto 0758dd6f46 Fixed clang build. 2018-06-27 11:12:34 -04:00
Pedro Pinto 83f9f3375c Fixed crash on client close. 2018-06-27 11:12:33 -04:00
Pedro Pinto 98a6616804 Removed current link in jetfix, it doesn't work well with docker. 2018-06-27 11:12:33 -04:00
Pedro Pinto b46f811cb5 Tweaks to pp custom build environment. 2018-06-27 11:12:33 -04:00
Eric Harding 7cf37bb080 SPARK-1407: Config off consolidated book. CMEChannel { book_type: BOOKTYPE_DIRECT } 2018-06-26 12:56:07 -04:00
John Colvin 6d918c0962 SPARK-1418 Remove constraint on creating NLegs spreads 2018-06-25 17:10:04 -04:00
Pedro Pinto 7cd052979e Fixed stop order price encoding 2018-06-22 16:29:45 -04:00
Pedro Pinto 3b3d44d6f6 Fixed the shared data problem that was crashing O'Grady. 2018-06-22 16:22:27 -04:00
Taha Afzal 2b2eaac979 SPARK-1416 (reactivated terminal status) 2018-06-21 19:50:32 -04:00
Taha Afzal 5b5274ab5f 1366 added hedging test 2018-06-21 18:02:08 -04:00
Jake Norbut 484ad656f9 SPARK-1409 - slow picker fixed? 2018-06-21 11:42:16 -05:00
Jake Norbut 0e92ee15f4 fix early orders unsub - exception was taking out the observable 2018-06-21 11:29:27 -05:00
Taha Afzal 2d5211ac9b SPARK-1366 n legs 2018-06-20 21:13:02 -04:00
Taha Afzal e62d116a44 SPARK-1366 n legs 2018-06-20 20:42:36 -04:00
Pedro Pinto 0deb4fbcf4 Fixed clang build. 2018-06-20 20:00:13 +00:00
Pedro Pinto b8b375eca5 Fixed crash on client close. 2018-06-20 20:00:13 +00:00
Pedro Pinto 4a42238cfb Removed current link in jetfix, it doesn't work well with docker. 2018-06-20 20:00:13 +00:00
Pedro Pinto d4081420ae Tweaks to pp custom build environment. 2018-06-20 20:00:13 +00:00
Taha Afzal 27d38cf443 SPARK-1400 spreader on_activate_recovered 2018-06-20 14:22:11 -04:00
Taha Afzal 4dad98a8b2 SPARK-1408 Limiter dupe value crash 2018-06-20 14:22:11 -04:00
Eric Harding a2b8d53ebb Add $HOST variable to install_config.json to make installer deploy easier 2018-06-20 11:22:00 -04:00
Eric Harding 33a1762cb7 Add sparkd web command for getting the web UI up in an emergency 2018-06-19 11:40:03 -04:00
Pedro Pinto f4f1bb7914 Fixed log msg. 2018-06-18 20:56:07 +00:00
John Colvin 908958ea3c SPARK-1386 format futures/future-spread-legs with exchange desc (if present) 2018-06-18 15:46:32 -04:00
Pedro Pinto 0a8bee2c31 Added a couple more digits to exchange price encoding. 2018-06-15 09:51:07 -04:00
Pedro Pinto 2dd8e64272 Perf analysis tools. 2018-06-15 09:51:07 -04:00
John Colvin aa704b8c96 timefilter - make text input filter more flexible 2018-06-14 20:56:19 -04:00
John Colvin 8673b857a7 timefilter - triple-click selects all time-text 2018-06-14 19:56:09 -04:00
John Colvin f83f2c7964 SPARK-1348 time-based period filter requires textbox to lose focus to accept new value 2018-06-14 19:13:18 -04:00
Eric Harding 86d8d7dafb Add seconds_to_recenter and max_open_ladders to UI.conf. Remove seconds and ticks from LadderSettings as it should not be stored per-ladder. 2018-06-14 18:35:15 -04:00
Eric Harding 5363fdd0db Reduce logging level for non-errors in PersistenceServiceServer 2018-06-14 18:08:52 -04:00
Eric Harding d3e7870b9a Fix build with the #ifdef stick 2018-06-14 18:08:32 -04:00
Eric Harding f05692e441 Merged Metrics into qa 2018-06-14 17:38:58 -04:00
Eric Harding 053c460ff6 Market data rate metrics reborn 2018-06-14 17:13:19 -04:00
Eric Harding af7e9dd3a8 Raise WebServer GET request logging to DIAG level 2018-06-14 17:11:57 -04:00
Eric Harding 3e3b6acfd7 Market data QueryServer response time/size. 2018-06-14 17:11:57 -04:00
Eric Harding b75496e665 Add sparkd memory use metric 2018-06-14 17:11:57 -04:00
Eric Harding 03864caa09 Add HistogramMetric type 2018-06-14 17:11:57 -04:00
Eric Harding 1558b79c16 Sparkd promethius metrics 2018-06-14 17:11:57 -04:00
Kevin Darby 57bfe5a85e added manual test cases for algos 2018-06-14 14:50:20 -04:00
John Colvin 3eaeaf0f35 fix LadderSettings equals method 2018-06-14 12:50:02 -04:00
Michael Harding ab3121cd5d Spark Admin: Admin user hack 2018-06-14 11:43:01 -04:00
Taha Afzal ec5bd4a164 SPARK-1332 2018-06-14 11:22:11 -04:00
John Colvin 3877aec09a SPARK-1354 Can't change bid, ask, or last colors in ladder 2018-06-14 10:37:29 -04:00
Jake Norbut fb17596304 updating test text 2018-06-14 08:32:48 -05:00
Michael Harding b36bcc66fb Update PnL manual test 2018-06-14 09:26:53 -04:00
Taha Afzal 73189fbbd8 2 admin limit tests changed slightly 2018-06-13 19:18:04 -04:00
John Colvin 484a9dba12 orderticket priceentrycontrol - update binding source in tickup/down command if has keyboard focus 2018-06-13 17:27:53 -04:00
John Colvin 83f2866604 SPARK-1349 Order Ticket Price Field - DoubleClick 2018-06-13 17:27:53 -04:00
Pedro Pinto 37f40046da Fixed typo in CTI code enum. 2018-06-13 15:48:17 -04:00
Pedro Pinto 2401b9db77 Typo in log msg. 2018-06-13 15:48:17 -04:00
John Colvin a45546690c SPARK-1352 ladder - auto-clear should only reset size to 0 2018-06-13 15:41:20 -04:00
John Colvin bc394ee3b5 SPARK-1350 Fill Window Comes Up Blank From Orders Window 2018-06-13 15:30:55 -04:00
Michael Harding b917fba19a Spark Admin: Assign correct default symbol to new productLimits rows 2018-06-13 14:58:54 -04:00
Michael Harding 63106eb866 SPARK-1245: Wrap Clip Limits help onto two lines 2018-06-13 14:28:27 -04:00
Eric Harding fa79bddb6b Make Spark webserver pipe down a bit 2018-06-13 13:49:34 -04:00
Pedro Pinto 83851c1494 QA assignments. 2018-06-13 10:35:02 -04:00
John Colvin 373f7ee3a1 SPARK-1208 synthetic modification disabled when it should not be 2018-06-12 23:50:56 -04:00
Taha Afzal 2ed8b9caf0 changes to the prehedge tests 2018-06-12 23:16:04 -04:00
John Colvin 470eee431e SPARK-1248 OrderBook - 'last message' cells are always empty 2018-06-12 23:08:00 -04:00
John Colvin e5e6723b3a orderbook - LiveSortingProperties were one step behind SortDescriptions 2018-06-12 22:54:42 -04:00
John Colvin 71be28911b SPARK-1243 crash on server bounce 2018-06-12 19:54:51 -04:00
Michael Harding 8ab3182c21 SPARK-1241: Username.ToLower 2018-06-12 17:33:20 -04:00
Pedro Pinto 508e2d4fcf SPARK-1234, Make sure rejected orders have valid price, quantity 2018-06-12 17:04:29 -04:00
Pedro Pinto 027e434ac9 SPARK-1236, Limit the number of exchange connection attempts. 2018-06-12 16:57:22 -04:00
Michael Harding e9be6d9859 SPARK-1245: Help text 2018-06-12 16:53:25 -04:00
Pedro Pinto 82e63f691c SPARK-1242, Added messaging governor to execution. 2018-06-12 16:01:35 -04:00
Pedro Pinto cb83ad08a6 Perf analysis tools. 2018-06-12 14:14:42 -04:00
Michael Harding e2e78d91c3 SPARK-1240, SPARK-1239: Hide Synthetic, Remove duplicate symbols 2018-06-12 13:21:28 -04:00
Michael Harding 427d03d340 SparkAdmin: Remove onclick from login 2018-06-12 11:29:53 -04:00
John Colvin 32e5111c54 orderbook - remove unused resources in OrderBookView.xaml 2018-06-11 15:11:53 -04:00
John Colvin ef12f81b73 orderbook - fix early-child sorting bug 2018-06-11 15:11:53 -04:00
Eric Harding c0f255f5a7 Spark Admin: Reload page if websocket is disconnected 2018-06-11 14:29:36 -04:00
John Colvin f6861820e4 orderbook - add apply/clear buttons to SubType filter popup 2018-06-11 12:19:26 -04:00
John Colvin 6a3960b33f FillTuple has SecurityMD field (instead of just Security) 2018-06-11 11:44:37 -04:00
Eric Harding e3858fd203 Merged in SPARK-1231 (pull request #39)
Persist should not throw exceptions. Nobody expects that

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2018-06-11 15:29:28 +00:00
Eric Harding 3f1d9307e9 Persist should not throw exceptions. Nobody expects that 2018-06-11 11:22:21 -04:00
Jake Norbut 5f7c2330f3 Ladder recenter on middle-click is now restricted to price column; this previously interferred with a badge's context menu from opening when the ladder was not centered 2018-06-11 09:56:48 -05:00
Jake Norbut 315b2a8b26 SPARK-1229 - EnumCharacterCasingConverter now checks for null 2018-06-11 09:55:29 -05:00
Taha Afzal 29ebfebcbc synthtic orders ressurected from disk show the correct filled price/qty 2018-06-10 23:32:02 -04:00
John Colvin 4fbaca6d05 orderbook - fix child-order filter bug 2018-06-10 17:15:25 -04:00
John Colvin 4d4f706e7c OrderBook - fix sorting bug (must recreate cell in addition to invoking PropertyChanged) 2018-06-10 16:26:46 -04:00
John Colvin 241396e292 clean up FillTupleSource/ObservableDemuxer 2018-06-10 15:59:52 -04:00
John Colvin 4e68c98cfb fix FillTupleSource 2018-06-08 23:36:06 -04:00
Eric Harding 30c1dfd9a7 Merged in SPARK-1123 (pull request #38)
SPARK-1123 on_orders.Replay().RefCount() does not handle late join correctly

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-06-09 01:37:48 +00:00
Pedro Pinto a64ca702c9 More tracing tools. 2018-06-08 18:24:07 -04:00
Taha Afzal 0a2b6ce17e SPARK-1212, delayed spreader order cancelling when no active achild orders 2018-06-08 18:21:29 -04:00
Pedro Pinto 660f203132 SPARK-1137, Server side tracing script. 2018-06-08 17:47:23 -04:00
Michael Harding 27f27dbcf1 AdminUI -> Spark Admin 2018-06-08 17:05:54 -04:00
Pedro Pinto 1ac6c8cd40 SPARK-1216, Formatted order prices properly. 2018-06-08 16:33:11 -04:00
Pedro Pinto a13cececa1 Formatted code a bit better. 2018-06-08 16:33:11 -04:00
Pedro Pinto 01d2e88097 Made logging more robust in the presence of config errors. 2018-06-08 16:33:11 -04:00
John Colvin 8f96cd07e3 FillTupleSource - actually update the state with incremental changes 2018-06-08 14:31:11 -04:00
Eric Harding b515e45b23 SPARK-1123 on_orders.Replay().RefCount() does not handle late join correctly 2018-06-08 13:49:22 -04:00
Eric Harding a37e2ead87 Hide status change messages in ladder 2018-06-08 13:44:34 -04:00
Kevin Darby 92ae592d31 Fixed a couple of bugs involving comparing doubles in the ladder and some ladder start crashes. 2018-06-08 13:10:50 -04:00
John Colvin db1cbf589f remove 'Latency' from Logger Subsystems in cs 2018-06-08 10:53:17 -04:00
Eric Harding a561ff3aae Fix merge problem with ObserveOn PR 2018-06-08 10:39:39 -04:00
Eric Harding 3eeab708d2 Merged in ObserveOn (pull request #37)
ObserveOnDispatcher -> ObserveOn + Lower priority so as not to block input

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-06-08 14:37:20 +00:00
Eric Harding 082056ea3f Fix crash in ErrorReporter because api thread is not STA 2018-06-08 09:53:32 -04:00
Eric Harding e9f3e92f82 Read spark.version for version information 2018-06-08 09:26:21 -04:00
Jake Norbut 152c79a4a9 Badge should never fill in with completion progress as it previously did 2018-06-08 08:05:35 -05:00
Eric Harding db877c859b ObserveOnDispatcher -> ObserveOn + Lower priority so as not to block input 2018-06-08 08:42:23 -04:00
John Colvin 1ec2423ad2 SPARK-1215 fills performance - do not copy dictionaries in FillTupleSource 2018-06-07 21:17:58 -04:00
Taha Afzal 1a88e52a08 subtle change in the QuoterWaitingForAck where book changes on the hedging side make it dissapear, hence order needs to be pulled 2018-06-07 20:06:40 -04:00
Taha Afzal a1b52d083d SPARK-1200 2018-06-07 17:50:09 -04:00
Michael Harding f364317cfa Better window border compromise 2018-06-07 17:10:16 -04:00
Michael Harding f474af1925 Update a few manual tests 2018-06-07 16:09:53 -04:00
Michael Harding 03ac5035fc AdminUI: Minor tweaks and fixes 2018-06-07 16:09:53 -04:00
Michael Harding e78d8208fa AdminUI: Add application name to login page 2018-06-07 16:09:53 -04:00
Pedro Pinto 7f8c591f75 Instrumenting server for perf measurements. 2018-06-07 13:58:02 -04:00
Taha Afzal 7d9685f995 SPARK-1211 cor id 2018-06-07 13:11:27 -04:00
John Colvin 699736ff52 Merged in badge-style-row-height-unification (pull request #35)
SPARK-1204 Ladder/Badge - row height needs to fit different badge styles

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2018-06-07 16:36:03 +00:00
John Colvin 8d0e7bc467 pr finishing touch 2018-06-07 12:35:03 -04:00
Michael Harding df784e9961 SPARK-1201: Fix and style table headers 2018-06-07 10:21:14 -04:00
Michael Harding af39aab153 AdminUI: Fix default security symbol for new rows 2018-06-07 10:21:13 -04:00
Taha Afzal 885c1ad9f8 SPARK-1152 remove limit in release.... 2018-06-06 23:26:07 -04:00
Eric Harding 91fdd6ec58 Improve generated structs by adding C# 7.2 readonly keyword 2018-06-06 20:21:20 -04:00
John Colvin 3adfd7056a Merge branch 'master' into badge-style-row-height-unification 2018-06-06 18:48:18 -04:00
Kevin Darby 5f845f1355 python 2.7 vs 3.6 blues 2018-06-06 17:56:53 -04:00
Pedro Pinto 5d4bb1d421 More server side perf instrumentation. 2018-06-06 17:53:18 -04:00
John Colvin 9836e46dd9 SPARK-1092 Inconsitencies in changing spreader params / probably lack of persistence 2018-06-06 17:21:42 -04:00
John Colvin bf9d5c2c6d ExecutionStrategyEditor - fix vertical grid line adorner 2018-06-06 17:21:42 -04:00
Taha Afzal ef1c3a7959 SPARK-1159 admin tool strange state 2018-06-06 16:59:35 -04:00
Taha Afzal b07f6aba59 comments, code style 2018-06-06 16:59:35 -04:00
Eric Harding a7b3b0fa6d SPARK-1197 MarketSegmentID 2018-06-06 16:55:03 -04:00
Jake Norbut f7a3b3ebc1 Fixing badge height overlapping level line 2018-06-06 15:49:46 -05:00
Michael Harding d291e4a022 SPARK-1201: Buttons, Transforms, and Stars 2018-06-06 16:13:51 -04:00
Pedro Pinto 292e5575d0 Made sim not look for secure logon file. 2018-06-06 16:06:43 -04:00
Pedro Pinto 1ad57dbf45 Instrumenting server side for performance tracking. 2018-06-06 16:06:43 -04:00
Kevin Darby b25f5556af added python27 to front of the path in vscode.cmd 2018-06-06 15:54:37 -04:00
Kevin Darby c10a4be411 fixing crash on spreader; the VMs get created no matter what security type, they're filtered later 2018-06-06 15:49:40 -04:00
Eric Harding de312c3023 Update c# language version to 7.2 2018-06-06 15:45:48 -04:00
Jake Norbut 93ff56c5cd Merged in badge_perf (pull request #36)
Badge perf

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-06-06 19:04:17 +00:00
Jake Norbut 5254b7367a PR suggestions; changing summary and description to backings as well since the tooltip was not changing as the lovm updated 2018-06-06 13:52:50 -05:00
Kevin Darby 6d2efb7dc9 clarified min_initial_tick_offset 2018-06-06 13:34:28 -04:00
Jake Norbut 50214741e7 PR - backings for a lazy life 2018-06-06 12:31:22 -05:00
Pete 56950b682d fat fingered 2018-06-06 13:00:45 -04:00
Jake Norbut 83b1bbf087 PR suggestions 2018-06-06 11:44:58 -05:00
Pete 203998e201 change default secure CME login to true 2018-06-06 12:40:25 -04:00
Kevin Darby 59d88d3cc1 colors per Michael 2018-06-06 12:32:13 -04:00
Kevin Darby 13f9717195 Merged in algos (pull request #30)
Algos
2018-06-06 16:15:06 +00:00
Kevin Darby 855e0d7ef9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-06-06 11:46:36 -04:00
Michael Harding 27a1cb277c SPARK-1201: Remove Connections table borders; Add colSpan to table cells 2018-06-06 11:18:59 -04:00
Michael Harding 3107d5f382 SPARK-1201: Add modal create dialog for Users and Accounts 2018-06-06 11:18:59 -04:00
Michael Harding e1940faeac AdminUI: Add Modal Dialog 2018-06-06 11:18:59 -04:00
Jake Norbut a2f22482f6 removing perf testing code; removing unused orders_cache 2018-06-06 09:36:21 -05:00
Taha Afzal ccea4571a6 SPARK-1206 uninitialized limit returned 2018-06-06 10:24:19 -04:00
Taha Afzal 3a72a4a82e Spreader's bookkeepingdata_ptr is a unique_ptr and some stylistic changes to shorten line widths 2018-06-06 10:11:39 -04:00
John Colvin ed6affb6b5 SPARK-1196 synth dialog - security text in column header is wrong (part 2) 2018-06-05 21:17:16 -04:00
John Colvin 2fc208ef74 SPARK-1205 Orderbook filter bug 2018-06-05 19:57:16 -04:00
Jake Norbut 64f86db86b misc cleanup 2018-06-05 18:07:05 -05:00
Jake Norbut 5e960479c7 slow badges no more 2018-06-05 18:01:04 -05:00
John Colvin 837aa51adf SPARK-1204 pr edits 2018-06-05 18:02:32 -04:00
Pedro Pinto d7a7b480ad Persist exchange book in sim even if merge_md is false. 2018-06-05 17:36:39 -04:00
Pedro Pinto d94afe9867 Tweaked performance analysis tools. 2018-06-05 16:59:54 -04:00
John Colvin 410e17aec5 SPARK-1204 Ladder/Badge - row height needs to fit different badge styles 2018-06-05 16:46:50 -04:00
Taha Afzal a8bd99af56 quoter's leg_prop_data is a ref into spreaderbookkeepingdata 2018-06-05 15:00:55 -04:00
John Colvin e11b5928ce SPARK-1181 touch up time filter dialog 2018-06-05 14:34:29 -04:00
Taha Afzal 6bcf352813 deleting cons's and =operators for SpreaderOrder 2018-06-05 14:10:07 -04:00
Michael Harding eeec7b0076 SPARK-1146: Settings Button in the ladder looks consistent 2018-06-05 13:33:18 -04:00
Michael Harding bacdb1c4cd SPARK-1201: Trading and admin permission columns for Users/Accounts Tab 2018-06-05 10:22:25 -04:00
Michael Harding 1e4ad28088 SPARK-1201: Security column in Secutity Limits 2018-06-05 10:03:32 -04:00
Michael Harding e013f93169 SPARK-1201: Show empty add dropdown 2018-06-05 10:03:32 -04:00
Kevin Darby 002c051c06 make it more clear that detach does not necessarily imply cancellation of the parent 2018-06-01 16:58:18 -04:00
Kevin Darby 42fd7b3f40 added state mutators for base class Pimpl...lovin that c++ 2018-06-01 16:46:28 -04:00
Kevin Darby f73d163312 implemented most of Pedro's review, need to do the mutators still 2018-06-01 15:36:06 -04:00
Kevin Darby ee124401b9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-06-01 13:46:27 -04:00
Kevin Darby ec7417a9c6 added comments to algo order headers and changed wording of a parameter 2018-06-01 13:46:10 -04:00
Eric Harding 536f4703ab Add visual studio perf session files to gitignore 2018-06-01 13:46:10 -04:00
Michael Harding 5f848a36e2 fix ui.fsproj 2018-06-01 13:46:10 -04:00
Michael Harding a7c714591a Fix Webpack config for development 2018-06-01 13:46:10 -04:00
Michael Harding 4655a68593 SPARK-1155 2018-06-01 13:46:10 -04:00
Eric Harding 9677d8996e SPARK-1189 - Windows C++ build is not producing symbols file 2018-06-01 13:46:10 -04:00
John Colvin 3f0a9e442e change UpdateDemuxer to TableUpdateDemuxer, which updates incrementally 2018-06-01 13:46:10 -04:00
Eric Harding f3387b57af SPARK-1183 3-5 1000 lot iceberg orders can lag/lock up the UI 2018-06-01 13:46:09 -04:00
John Colvin abed4bbff6 CVOrderBook - fix typeo that trapped the root_node() fn in an endless loop 2018-06-01 13:46:09 -04:00
Eric Harding bb90429ada Fix problem where you can't see price moves when mouse is over a cell in the ladder 2018-06-01 13:46:09 -04:00
Kevin Darby 1efbc2fd45 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-05-31 16:37:00 -04:00
Kevin Darby 04027ed980 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-05-29 15:26:55 -04:00
Kevin Darby 5a850c314c Merge branch 'algos' of bitbucket.org:bluetradesys/spark into algos 2018-05-29 15:26:08 -04:00
Kevin Darby b566668d83 pushd, a better cd 2018-05-29 11:52:17 -04:00
Kevin Darby 6679695931 fixed cancel state in TopOrder 2018-05-26 17:01:04 -04:00
Kevin Darby 23a8112dd3 Merge branch 'algos' of bitbucket.org:bluetradesys/spark into algos 2018-05-26 15:13:22 -04:00
Kevin Darby b9a07a8e21 toporder, maybe 2018-05-26 15:13:13 -04:00
Kevin Darby 86deaf2edb Join order works and hedges 2018-05-26 15:13:13 -04:00
Kevin Darby fc347250e3 pushing for join to look at joinordervm validation 2018-05-26 15:13:13 -04:00
Kevin Darby aae867a555 doskey, alive and well 2018-05-26 15:13:13 -04:00
Kevin Darby 37a454cf51 behold, TickOrder 2018-05-26 15:13:13 -04:00
Kevin Darby 5162b9f0c0 toporder, maybe 2018-05-26 15:12:55 -04:00
Taha Afzal ad757495bd comments 2018-05-25 18:42:30 -04:00
Kevin Darby e473c18a30 Join order works and hedges 2018-05-25 18:33:30 -04:00
Taha Afzal 640ec0b1ed spreader tests 2018-05-25 18:06:19 -04:00
Jake Norbut c3e2622a22 Ladder - restoring badge context menu and hooking up crack 2018-05-25 18:06:19 -04:00
John Colvin 7d05f76fe4 DIYGrid HighPerformanceRow - InvalidateVisual when DataContext changes 2018-05-25 18:06:19 -04:00
Kevin Darby 7a8a38f89d pushing for join to look at joinordervm validation 2018-05-25 18:04:58 -04:00
Michael Harding 1e54cb1daf Order Type Colors 2018-05-25 18:04:57 -04:00
Eric Harding 7b714018d2 SPARK-1139 Make Combox faster when system is under load (no AllowsTransparency) 2018-05-25 18:04:57 -04:00
Taha Afzal c3371d0971 re-org'ing SpreaderUtils functions into cpp 2018-05-25 18:04:57 -04:00
Kevin Darby 322e3d81ba doskey, alive and well 2018-05-24 18:24:10 -04:00
Taha Afzal 4ada5a89f4 SPARK-1119 completed 2018-05-24 18:24:10 -04:00
Eric Harding 15aa016aa0 Reduced CPU for cert market data during development 2018-05-24 18:24:10 -04:00
Taha Afzal e77f3169de SPARK-1119 (deleting unused API functions) 2018-05-24 18:24:10 -04:00
Kevin Darby 660c5d3a06 behold, TickOrder 2018-05-24 16:22:19 -04:00
316 changed files with 8944 additions and 3212 deletions

No files matched your search

+2
View File
@@ -21,3 +21,5 @@ src/client/sparkcmd/Properties/launchSettings.json
node_modules
paket-files
project.lock.json
src/server/sparkd/orderman/perf_analysis/data
src/server/sparkd/orderman/perf_analysis/trace_data/
+1
View File
@@ -39,6 +39,7 @@
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
<Exec Command="
set PATH=$(spark_build_python_path);%path% &amp;
set PYTHONPATH=$(Externals)\python &amp;
$(protoc) ^
%(Proto3.FullPath) ^
+1 -1
View File
@@ -25,7 +25,7 @@ namespace bts.ui {
public AboutDaemon(IBus bus, [Import("username")]string username, IBrowserWindowManager browser) {
_user = username;
_browser = browser;
_dispose = bus.subscribe(SparkTags.About).ObserveOnDispatcher().Subscribe(_ => show_about());
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
}
[Conditional("DEBUG")]
@@ -18,7 +18,7 @@ namespace bts {
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model) {
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
.ObserveOnDispatcher()
.OnDispatcher()
.Subscribe(_delete_synthetic);
}
+12 -9
View File
@@ -7,7 +7,7 @@ using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Threading;
using System.Windows;
namespace bts.spark
@@ -22,18 +22,21 @@ namespace bts.spark
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
_svc = svc;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).ObserveOnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
}
public void Dispose() {
Disposable.dispose(ref _dispose);
}
public async Task report_error(string message, string title = null, MessageBoxImage? image = null) {
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
if (result == BTSMessageBoxButtons.Support) {
show_support_window(message, title);
}
public void report_error(string message, string title = null, MessageBoxImage? image = null) {
// Error reports are likely to arrive the api thread (which is not an STA thread)
Application.Current.Dispatcher.BeginInvoke(async () => {
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
if (result == BTSMessageBoxButtons.Support) {
show_support_window(message, title);
}
});
}
public void show_support_window(string message, string title) {
@@ -70,11 +73,11 @@ namespace bts.spark
return (get_successful_rv(result.value), string.Empty);
else {
string error_msg = template.fmt(error);
error_reporter.report_error(error_msg).ignore();
error_reporter.report_error(error_msg);
return (get_unsuccessful_rv(), error_msg);
}
} else {
error_reporter.report_error(result.error_message).ignore();
error_reporter.report_error(result.error_message);
return (get_unsuccessful_rv(), result.error_message);
}
}
@@ -28,11 +28,11 @@ namespace bts.shell {
_model = model;
_error_reporter = error_reporter;
_dispose = Disposable.Create(
model.on_orders.ObserveOnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).ObserveOnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowModifyOrderTag).ObserveOnDispatcher().Subscribe(_show_modify_order)
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
);
}
@@ -23,10 +23,10 @@ namespace bts {
public SecurityPickerDaemon(IBus bus) {
_bus = bus;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.ShowPickOne).ObserveOnDispatcher().Subscribe(show_pick_one),
bus.subscribe(SparkTags.FilterPickOne).ObserveOnDispatcher().Subscribe(show_filtered_pick_one),
bus.subscribe(SparkTags.ShowPickMultiple).ObserveOnDispatcher().Subscribe(show_pick_multiple),
bus.subscribe(SparkTags.ShowAndEditPickMultiple).ObserveOnDispatcher().Subscribe(show_and_edit_pick_multiple)
bus.subscribe(SparkTags.ShowPickOne).OnDispatcher().Subscribe(show_pick_one),
bus.subscribe(SparkTags.FilterPickOne).OnDispatcher().Subscribe(show_filtered_pick_one),
bus.subscribe(SparkTags.ShowPickMultiple).OnDispatcher().Subscribe(show_pick_multiple),
bus.subscribe(SparkTags.ShowAndEditPickMultiple).OnDispatcher().Subscribe(show_and_edit_pick_multiple)
);
}
+2 -4
View File
@@ -47,7 +47,7 @@ namespace mbts.ui
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
public static ImageSource Date { get { return load_image("Date.png"); } }
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
public static ImageSource Algo { get { return Cube3d; } }
public static ImageSource Quote { get { return Math; } }
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
@@ -62,9 +62,7 @@ namespace mbts.ui
#pragma warning restore CS0618
// Vector icons
public static ImageSource Favorites { get { return Favorites_Gold; } }
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

+3 -19
View File
@@ -3,33 +3,17 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mbts="clr-namespace:mbts.ui">
<DrawingImage x:Key="FavoritesGold">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<SolidColorBrush Color="#ffd932" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="FavoritesWhite">
<DrawingImage x:Key="Bookmarks">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
<Pen Thickness="3" Brush="Black" />
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<SolidColorBrush Color="#ffd932" />
<SolidColorBrush Color="Transparent" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
@@ -0,0 +1,81 @@
using bts.spark;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace bts.ui {
[Export(typeof(IShellModule))]
public class BookmarkShellModule : ILazyMenuModule {
readonly WindowManager _wm;
readonly IBookmarks _bookmarks;
readonly ISavedWindowFactory _window_factory;
readonly WorkspacesModel _workspaces;
[ImportingConstructor]
public BookmarkShellModule(
WorkspacesModel workspaces,
WindowManager wm,
IBookmarks bm,
ISavedWindowFactory swf) {
_workspaces = workspaces;
_wm = wm;
_bookmarks = bm;
_window_factory = swf;
}
public ImageSource icon => Icons.Bookmarks;
public string name => "My Windows";
public int priority => 35;
public IEnumerable<SparkCommand> actions {
get {
// The windows that are open
var open =
_wm.list_documents()
.Select(td => td.ViewModel)
.Where(vm => vm.HasCustomTitle)
.Select(vm =>
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
.Cast<SparkCommand>();
var open_set = new HashSet<string>(open.Select(c => c.title));
// The windows that are not open
var closed =
_bookmarks.bookmarks
.Where(state => !open_set.Contains(state.title))
.Select(state =>
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
() => {
_bookmarks.remove(state);
return _window_factory.restore(state);
}))
.Cast<SparkCommand>();
// todo: The windows that are closed
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
yield return w;
}
}
}
public Task<IShellWindow> create(Stream load_stream) {
// This module shouldn't have any windows
throw new NotImplementedException();
}
public void Dispose() { }
}
}
@@ -0,0 +1,48 @@
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace mbts.ui.shell {
public interface IBookmarks {
void save_bookmark(IShellWindow window);
void save_bookmark(WindowState window);
void remove(WindowState window);
void clear();
IEnumerable<WindowState> bookmarks { get; }
}
[Export(typeof(IBookmarks))]
public class BookmarksModel : IBookmarks {
readonly ISettingsCollector _settings_collector;
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
[ImportingConstructor]
public BookmarksModel(ISettingsCollector settings) {
_settings_collector = settings;
}
public void save_bookmark(IShellWindow window) {
var state = _settings_collector.get_window_state(window);
if (state != null) {
_bookmarks[state.title] = state;
}
}
public void save_bookmark(WindowState window) {
_bookmarks[window.title] = window;
}
public void clear() {
_bookmarks.Clear();
}
public void remove(WindowState window) {
_bookmarks.Remove(window.title);
}
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
}
}
@@ -78,11 +78,11 @@ namespace mbts.spark {
_animation.AnimationsChanged += on_animations_changed;
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
_dispose = Disposable.Create(
_orders_model.on_security_orders(security.Id).ObserveOnDispatcher().Subscribe(_on_orders),
_position.ObserveOnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_inside_bids.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.ObserveOnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
_orders_model.on_security_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
);
}
@@ -81,7 +81,7 @@ namespace mbts.spark {
}
//
_resources.AddRange(
state.ObserveOnDispatcher().Subscribe(s => {
state.OnDispatcher().Subscribe(s => {
using (rows_view.DeferRefresh()) {
rows_view.SetSortingCriteria(s.sorting_criteria);
var filters = s.filters;
@@ -57,7 +57,7 @@ namespace mbts.spark {
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count < 3, secs => new SecurityCriteria(secs))
};
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(c =>
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
}
@@ -16,7 +16,7 @@ namespace bts.shell {
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).ObserveOnDispatcher().Subscribe(_show);
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
}
public override Task<IShellWindow> load(StartupData state) =>
@@ -47,8 +47,8 @@ namespace mbts.spark {
_vm = new SingleValuePump<TimeAndSalesVM>();
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("TimeAndSales Callback");
var update_securities_cmd = SparkCommand.create("Update Time and Sales", Icon, update_securities_tag);
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).ObserveOnDispatcher();
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).ObserveOnDispatcher();
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).OnDispatcher();
_commands = new SparkCommand[] {
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
@@ -56,7 +56,7 @@ namespace mbts.spark {
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria)
};
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(update_criteria);
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(update_criteria);
_initialize();
bool always_true<T>(T _) => true;
@@ -23,7 +23,7 @@ namespace bts.shell {
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowTimeAndSalesTag).ObserveOnDispatcher().Subscribe(show)
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
);
}
@@ -34,7 +34,7 @@ namespace bts.ui {
public int priority => 40;
public IEnumerable<SparkCommand> actions => new []{
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
SparkCommands.ShowWorkspaces
//SparkCommands.ShowSettings
};
@@ -15,34 +15,33 @@
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ParameterSectionView.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="GroupLevelIndent">18</sys:Double>
<sys:Double x:Key="RowHeaderWidth">285</sys:Double>
<sys:Double x:Key="RowHeight">16</sys:Double>
<sys:Double x:Key="NegativeRowHeight">-16</sys:Double>
<sys:Double x:Key="ItemsHostCanBeDisabledCollapsedHeight">15</sys:Double>
<Thickness
x:Key="GroupLevelMargin"
Left="{StaticResource GroupLevelIndent}"
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
/>
<Thickness
x:Key="GroupContentAlignedWithHeaderMargin"
Left="{StaticResource GroupLevelIndent}"
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
Top="{StaticResource NegativeRowHeight}"
/>
<cv:SyntheticPriceCalcDisplayTextConverter x:Key="SyntheticPriceCalcDisplayConverter"/>
<cv:TickFactorsTextConverter x:Key="TickFactorsTextConverter"/>
<usp:RowHeaderGroupIndentConverter
x:Key="RowHeaderGroupIndentConverter"
Indent="{StaticResource GroupLevelIndent}"
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
/>
<usp:RowHeaderGroupIndentConverter
x:Key="ReverseRowHeaderGroupIndentConverter"
Indent="{StaticResource GroupLevelIndent}"
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
Reverse="True"
/>
<usp:RowGroupExpanderWidthConverter
x:Key="RowGroupExpanderWidthConverter"
Indent="{StaticResource GroupLevelIndent}"
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
Offset="{StaticResource RowHeight}"
RowHeaderWidth="{StaticResource RowHeaderWidth}"
/>
@@ -293,7 +292,7 @@
</DataTrigger>
</Style.Triggers>
</Style>
<DataGridTextColumn x:Key="AddColumnPlaceholder" x:Shared="False" IsReadOnly="True">
<DataGridTextColumn x:Key="{x:Static usp:ExecutionStrategyEditor.AddColumnPlaceholderKey}" x:Shared="False" IsReadOnly="True">
<DataGridTextColumn.Header>
<c:PopupControl
ShowExpanderArrow="False" PopupPlacement="Right" Header="Add Leg" Padding="6,0"
@@ -308,7 +307,7 @@
</c:PopupControl>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataTemplate x:Key="RemovableColumnHeaderTemplate">
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -321,7 +320,9 @@
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.NonRemovableColumnHeaderTemplateKey}">
<TextBlock Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
</DataTemplate>
<ControlTemplate x:Key="ExecutionStrategyEditorTemplate" TargetType="usp:ExecutionStrategyEditor">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
@@ -25,7 +25,13 @@ namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ExecutionStrategyEditor.xaml
/// </summary>
partial class ExecutionStrategyEditor : Control {
partial class ExecutionStrategyEditor : Control {
public const string
RemovableColumnHeaderTemplateKey = "RemovableColumnHeaderTemplate",
NonRemovableColumnHeaderTemplateKey = "ReadOnlyColumnHeaderTemplate",
AddColumnPlaceholderKey = "AddColumnPlaceholder";
public const double GroupLevelIndent = 18;
static ExecutionStrategyEditor() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExecutionStrategyEditor), new FrameworkPropertyMetadata(typeof(ExecutionStrategyEditor)));
@@ -196,7 +202,7 @@ namespace mbts.ui.spark {
if (dg != null) {
var legs = LegTuples;
dg.Columns.SetItems(legs.Select(_column_factory));
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource("AddColumnPlaceholder"));
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource(AddColumnPlaceholderKey));
_dg_line_adorner = AdornerHelper.attach_continuously_on_loaded(() => new ExecutionStrategyGridLineAdorner(dg), dg);
_legs_collection_changed_sub = (legs as INotifyCollectionChanged)?.Subscribe((s, e) => dg.Columns.ReflectChange(legs, _column_factory, e));
}
@@ -204,7 +210,7 @@ namespace mbts.ui.spark {
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
if (IsNew) col.HeaderTemplate = (DataTemplate)FindResource("RemovableColumnHeaderTemplate");
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
return col;
}
@@ -240,7 +246,7 @@ namespace mbts.ui.spark {
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
var rv = new ContentControl();
rv.SetBinding(ContentControl.ContentProperty, new Binding());
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding("data_type") { Converter = cell_template_converter });
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
return rv;
}
}
@@ -25,7 +25,10 @@ namespace mbts.ui.spark
void _items_presenter_SizeChanged(object sender, SizeChangedEventArgs e) { InvalidateVisual(); }
protected override void draw_vertical_line(DrawingContext dc, double x, DataGridExVerticalLine line) {
if (_items_presenter != null) dc.DrawLine(line.pen.Value, new Point(x - 10, 58), new Point(x - 10, _items_presenter.ActualHeight + 58));
x -= GroupLevelIndent;
var start = _grid.TranslatePoint(new Point(x, _grid.super_header_height + _col_header_presenter.ActualHeight), this);
var stop = _grid.TranslatePoint(new Point(x, _grid.ActualHeight), this);
dc.DrawLine(line.pen.Value, start, stop);
}
}
}
@@ -98,9 +98,9 @@ namespace bts.shell {
void IInitializable.initialize() {
Disposable.dispose(ref _dispose);
_dispose = Disposable.Create(
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).ObserveOnDispatcher().Subscribe(_show_synthetic_create),
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).ObserveOnDispatcher().Subscribe(_show_synthetic_strategy),
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).ObserveOnDispatcher().Subscribe(_show_synthetic_clone)
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).OnDispatcher().Subscribe(_show_synthetic_create),
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).OnDispatcher().Subscribe(_show_synthetic_strategy),
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).OnDispatcher().Subscribe(_show_synthetic_clone)
);
}
@@ -32,14 +32,14 @@ namespace mbts.spark {
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
() => new SecurityCriteria(), () => true
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
_vm.observable.SelectMany(vm => vm.current_row_observable),
can_row_change, r => r == null ? 0 : r.security_key
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_mod_params)),
can_execute, select_security_id
), SparkCommand.create("Change", Icons.ModifySynthetic, SparkTags.ShowSyntheticStrategyTag).complete(
_vm.observable.SelectMany(vm => vm.current_row_observable),
can_row_change, r => r == null ? SecurityDataHelper.Defaults.SecurityImmutable : r.security
r => r != null, r => r?.security ?? SecurityDataHelper.Defaults.SecurityImmutable
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
_vm.observable.SelectMany(vm => vm.current_row_observable),
r => r != null && r.strategy.HasValue, r => r == null ? default : (r.security, r.strategy.Value)
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_strategy)),
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder.complete(
_vm.observable.SelectMany(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
@@ -49,10 +49,19 @@ namespace mbts.spark {
})
};
bool can_row_change(SyntheticManagementDialogVM.Row r) =>
r != null && r.n_working_orders == 0;
}
IObservable<RowModificationParams?> observe_row_mod_params(SyntheticManagementDialogVM.Row r) =>
r == null ? Observable.Return(new RowModificationParams?()) :
_orders_model.security_has_working_orders(r.security_key).Select(hwo => RowModificationParams.create(r.security, hwo));
IObservable<RowStrategy?> observe_row_strategy(SyntheticManagementDialogVM.Row r) =>
r == null ? Observable.Return(new RowStrategy?()) :
_orders_model.spreader.security_strategy(r.security.Id).Select(strategy => RowStrategy.create(r.security, strategy));
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
}
public void initialize(StartupData state) {
Logger.notnull(state);
_state = state;
@@ -71,5 +80,31 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
}
struct RowModificationParams {
public readonly SecurityImmutable security;
public readonly bool has_working_orders;
RowModificationParams(SecurityImmutable security, bool has_working_orders) {
this.security = security;
this.has_working_orders = has_working_orders;
}
public static RowModificationParams? create(SecurityImmutable security, bool has_working_orders) =>
new RowModificationParams(security, has_working_orders);
}
struct RowStrategy {
public readonly SecurityImmutable security;
public readonly ExecutionStrategyImmutable strategy;
RowStrategy(SecurityImmutable security, ExecutionStrategyImmutable strategy) {
this.security = security;
this.strategy = strategy;
}
public static RowStrategy? create(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
new RowStrategy(security, strategy);
}
}
}
@@ -16,7 +16,7 @@ namespace bts.shell {
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(StartupData state) =>
+2 -2
View File
@@ -10,7 +10,7 @@ namespace mbts.spark {
readonly Property<bool> _enabled;
protected CellBase(IRowModel model, LegSpreaderParamsTuple leg_tup) =>
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).ObserveOnDispatcher());
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).OnDispatcher());
public bool enabled => _enabled.Value;
}
@@ -24,7 +24,7 @@ namespace mbts.spark {
: base(model, leg_tup) {
_model = model;
_leg_tup = leg_tup;
_override_value_sub = model.observe_override_value(leg_tup).ObserveOnDispatcher().Subscribe(v => {
_override_value_sub = model.observe_override_value(leg_tup).OnDispatcher().Subscribe(v => {
value = v;
RaisePropertyChanged(nameof(value));
});
@@ -44,10 +44,10 @@ namespace mbts.spark {
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
title = $"Edit '{synthetic.Synthetic.Name}'";
icon = Icons.ModifySynthetic;
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.ObserveOnDispatcher());
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
var commit = new ObservantAsyncCommand(commit_async);
_resources.Add(security_has_working_orders.Select(v => !v).ObserveOnDispatcher().Subscribe(commit));
_resources.Add(_has_working_orders.ObserveOnDispatcher().Subscribe(vals => {
_resources.Add(security_has_working_orders.Select(v => !v).OnDispatcher().Subscribe(commit));
_resources.Add(_has_working_orders.OnDispatcher().Subscribe(vals => {
if (vals.newval)
set_error(nameof(has_working_orders), "Security has working orders");
else clear_error(nameof(has_working_orders));
@@ -173,7 +173,8 @@ namespace mbts.spark {
}
_is_setting_price_mults = false;
};
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
} else {
_can_add_leg = backing(nameof(can_add_leg), false);
}
@@ -66,7 +66,7 @@ namespace mbts.spark {
.Select(t => (uint)(t.state
.Where(kv => kv.Value.is_active())
.Count())
).ObserveOnDispatcher());
).OnDispatcher());
name = security.Synthetic.Name;
n_legs = security.Synthetic.Legs.Count();
description = SecurityFormatter.format_contract_legs(security.Synthetic.Legs);
@@ -38,7 +38,7 @@ namespace bts.shell
}
public void thread_init() {
_dispose = _message_src.ObserveOnDispatcher().Subscribe(_messages.AddLog);
_dispose = _message_src.OnDispatcher().Subscribe(_messages.AddLog);
_content =
new LogViewer {
ItemsSource = _messages,
@@ -23,7 +23,7 @@ namespace bts.shell {
public HelpShellModule(IBus bus, IBrowserWindowManager browser) {
_awf = new ActivityWindowFactory(this, bus);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.ShowUri).ObserveOnDispatcher().Subscribe(u => browser.Pages.Add(u))
bus.subscribe(SparkTags.ShowUri).OnDispatcher().Subscribe(u => browser.Pages.Add(u))
);
}
@@ -24,13 +24,16 @@ namespace mbts.spark {
public FillsVM(IBus bus, OrdersModel model, StartupData state)
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
FillsVM(IBus bus, OrdersModel model, IObservable<ITableUpdateTransform<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateTableUpdate(fills_source), sorting_criteria) {
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
_model = model;
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts(), r => r.account, nameof(Row.account));
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users(), r => r.user, nameof(Row.user));
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added, t.removed)),
fills_source.with_respect_to(
ft => ft.security_md.HasValue ? ft.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable,
s => s.Id, ft => ft.key
).Select(t => (t.added.Values, t.removed.Values)),
r => r.security,
SecurityCriteria.from_securities,
SecurityCriteria.get_to_securities(bus),
@@ -126,8 +129,8 @@ namespace mbts.spark {
protected override Row row_factory(FillTuple row_model) => new Row(row_model, _model);
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.fill_time), false); }
static IObservable<ITableUpdateTransform<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
if (state.orders != null && state.orders.Length == 0) {
static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples;
@@ -138,7 +141,7 @@ namespace mbts.spark {
readonly FillImmutable _fill;
readonly OrderRecordImmutable _order;
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security ?? SecurityDataHelper.Defaults.Security) {
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
_fill = fill_tuple.fill;
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
_model = model;
@@ -18,7 +18,7 @@ namespace bts.shell {
: base(module, "Fills") {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowFillsTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(StartupData state) =>
@@ -30,12 +30,11 @@ namespace bts.shell {
_wm = wm;
_settings_hub = settings_hub;
_orders_model = orders_model;
var window_props = Configuration.get<bts.config.IShellWindowProps>();
_max_ladders_open = (int)(window_props?.ladder?.max_total_open ?? 75);
_max_ladders_open = (int)(Configuration.get<bts.config.LadderKnobs>().max_open_ladders);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).ObserveOnDispatcher().Subscribe(s => show_windows(new[] { s })),
bus.subscribe(SparkTags.ShowLaddersTag).ObserveOnDispatcher().Subscribe(show_windows)
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
);
}
@@ -77,6 +76,7 @@ namespace bts.shell {
s.ask_break_line = LadderSettings.Default.ask_break_line;
if (s.mid_break_line == null)
s.mid_break_line = LadderSettings.Default.mid_break_line;
s.row_height = LadderRender.measure_row_height(s);
return s;
}
}
@@ -17,7 +17,7 @@ namespace bts.shell {
_bus = bus;
_model = model;
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(OrderBookStartupData state) {
@@ -17,7 +17,7 @@ namespace bts.shell {
_bus = bus;
_model = model;
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
}
@@ -24,7 +24,7 @@ namespace mbts.spark {
var security_source = security_md_source.Select(smd => smd.security);
//
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
properties_vm = new OrderPropertiesEditorVM(model, security_source.ObserveOnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
properties_vm = new OrderPropertiesEditorVM(model, security_source.OnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
_price = backing(nameof(price), double.NaN);
size_calculator = new CalculatorLogic(null, calculator_buttons);
_current_price = backing(nameof(current_price), order_source.Select(ort => ort.order.State.Price));
@@ -83,15 +83,15 @@ namespace mbts.spark {
size_calculator,
properties_vm,
bbo = new BBOVM(security_md_source),
order_source.ObserveOnDispatcher().Subscribe(ort => {
order_source.OnDispatcher().Subscribe(ort => {
key = ort.order.Key;
if (DoubleHelper.IsNaNOrInfinity(price))
price = ort.order.State.Price;
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
size_calculator.bypass_value_setter(ort.order.State.Quantity);
}),
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).ObserveOnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().ObserveOnDispatcher().Subscribe(modify_order));
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
this.modify_order = modify_order;
set_price_command = new DelegateCommand<double>(_price.set);
}
+33 -12
View File
@@ -1,32 +1,53 @@
using bts.api;
using bts.api.orderman;
using mbts.utils;
using System;
namespace mbts.spark
{
namespace mbts.spark {
class FillTuple : IEquatable<FillTuple> {
public readonly FillImmutable fill;
public readonly OrderRecordImmutable? order;
public readonly SecurityImmutable? security;
public readonly string key;
public readonly SecurityMD? security_md;
public readonly FillTupleKey key;
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityImmutable? security) {
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityMD? security_md) {
this.fill = fill;
this.order = order;
this.security = security;
// a FillTuple that DOES NOT HAVE associated order/security needs to be distinguishable
// from a FillTuple (of the same underlying FillImmutable) that DOES HAVE associated order/security
key = fill.FillKey;
if (!order.HasValue) key += "!o";
if (!security.HasValue) key += "!s";
this.security_md = security_md;
key = new FillTupleKey(fill.FillKey, order.HasValue ? order.Value.Key : 0u, security_md.HasValue ? security_md.Value.security.Id : 0u);
}
public bool Equals(FillTuple other) => other != null
&& other.order.HasValue == order.HasValue
&& other.security.HasValue == security.HasValue
&& other.security_md.HasValue == security_md.HasValue
&& other.fill.Equals(fill);
public override bool Equals(object obj) => Equals(obj as FillTuple);
public override int GetHashCode() => key.GetHashCode();
}
struct FillTupleKey : IEquatable<FillTupleKey> {
public readonly string fill_key;
public readonly uint order_key, security_key;
public FillTupleKey(string fill_key, uint order_key, uint security_key) {
this.fill_key = fill_key;
this.order_key = order_key;
this.security_key = security_key;
}
public bool Equals(FillTupleKey other) =>
other.order_key == order_key &&
other.security_key == security_key &&
other.fill_key == fill_key;
public override bool Equals(object obj) =>
obj is FillTupleKey k && Equals(k);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
fill_key.GetHashCode(),
(int)order_key,
(int)security_key);
}
}
+133 -128
View File
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.core;
using bts.api.core;
using bts.api.orderman;
using mbts.collections;
using mbts.utils;
@@ -7,23 +6,25 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
namespace mbts.spark {
class FillTupleSource : IDisposable {
public static IObservable<ITableUpdateTransform<FillTuple, string>> create_observable(
class FillTupleSource : IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>>, IDisposable {
// returns a ref-count Observable.Create-wrapper that initializes/destroys the [hot-observable] FillTupleSource.
// (initializes when the first subscription is made, and destroys when the last subscription is disposed).
public static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> create_observable(
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
IObservable<TableUpdate<FillImmutable, string>> fills
) {
IObservable<TableUpdate<FillImmutable, string>> fills) {
FillTupleSource source = null;
int ref_count = 0;
return Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
return Observable.Create<IIncrementalUpdate<FillTuple, FillTupleKey>>(observer => {
if (Interlocked.Increment(ref ref_count) == 1) {
SparkUtils.lazy_assert(source == null, () => fmt_assert_msg("source should be null, there must be a leak."));
source = new FillTupleSource(securities, orders, fills);
}
IDisposable sub = source.fill_tuples.Subscribe(observer);
IDisposable sub = source.Subscribe(observer);
return Disposable.Create(() => {
Disposable.dispose_once(ref sub);
if (Interlocked.Decrement(ref ref_count) == 0)
@@ -33,153 +34,157 @@ namespace mbts.spark {
string fmt_assert_msg(string msg) => $"{nameof(FillTupleSource)}.{nameof(create_observable)}: {msg}";
}
readonly Subject<IIncrementalUpdate<FillTuple, FillTupleKey>> _subject = new Subject<IIncrementalUpdate<FillTuple, FillTupleKey>>();
readonly Dictionary<FillTupleKey, FillTuple> _state = new Dictionary<FillTupleKey, FillTuple>();
readonly SetMap<uint, string> _order_fills = new SetMap<uint, string>();
readonly SetMap<uint, string> _security_fills = new SetMap<uint, string>();
IReadOnlyDictionary<string, FillImmutable> _fills_cache = new Dictionary<string, FillImmutable>(0);
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>(0);
IReadOnlyDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
// _n_write_ops should never be greater than 1, bc all 3 input observables fire 'OnDispatcher'
int _n_read_ops, _n_write_ops;
IDisposable _dispose;
FillTupleSource(
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
IObservable<TableUpdate<FillImmutable, string>> fills) {
State state = new State();
var src = Observable.FromEvent<Action<TableUpdateTransform<FillTuple, string>>, TableUpdateTransform<FillTuple, string>>(cb => _updated += cb, cb => _updated -= cb);
fill_tuples = Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
var init_state = state.fill_tuples.state.ToDictionary();
observer.OnNext(new TableUpdateTransform<FillTuple, string>(init_state, init_state.Values.ToList(), new List<FillTuple>(0)));
return src.Subscribe(observer);
});
IObservable<TableUpdate<FillImmutable, string>> fills) =>
//
_dispose = Disposable.Create(
securities.ObserveOnDispatcher().Subscribe(s => update(state.update_securities(s))),
orders.ObserveOnDispatcher().Subscribe(o => update(state.update_orders(o))),
fills.ObserveOnDispatcher().Subscribe(f => update(state.update_fills(f)))
);
void update(State new_state) {
state = new_state;
_updated?.Invoke(state.fill_tuples);
securities.OnDispatcher().Subscribe(_update_securities),
orders.OnDispatcher().Subscribe(_update_orders),
fills.OnDispatcher().Subscribe(_update_fills));
public IDisposable Subscribe(IObserver<IIncrementalUpdate<FillTuple, FillTupleKey>> observer) {
using (_read()) {
observer.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(new Dictionary<FillTupleKey, FillTuple>(_state), new Dictionary<FillTupleKey, FillTuple>(0)));
return _subject.Subscribe(observer);
}
}
event Action<TableUpdateTransform<FillTuple, string>> _updated;
public void Dispose() => Disposable.dispose(ref _dispose);
IObservable<TableUpdateTransform<FillTuple, string>> fill_tuples { get; }
public void Dispose() =>
Disposable.dispose(ref _dispose);
class State {
readonly IReadOnlyDictionary<string, FillImmutable> _fills;
readonly IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
readonly IReadOnlyDictionary<uint, SecurityMD> _securities;
readonly TableUpdateTransform<FillTuple, string> _fill_tuples;
readonly SetMap<uint, string> _order_fills;
readonly SetMap<uint, string> _security_fills;
public State() : this(
new Dictionary<string, FillImmutable>(),
new Dictionary<uint, OrderRecordImmutable>(),
new Dictionary<uint, SecurityMD>(),
new TableUpdateTransform<FillTuple, string>(new Dictionary<string, FillTuple>(), new List<FillTuple>(), new List<FillTuple>()),
new SetMap<uint, string>(),
new SetMap<uint, string>()
) { }
State(
IReadOnlyDictionary<string, FillImmutable> fills,
IReadOnlyDictionary<uint, OrderRecordImmutable> orders,
IReadOnlyDictionary<uint, SecurityMD> securities,
TableUpdateTransform<FillTuple, string> fill_tuples,
SetMap<uint, string> order_fill_tuples,
SetMap<uint, string> security_fill_tuples
) {
_fills = fills;
_orders = orders;
_securities = securities;
_fill_tuples = fill_tuples;
_order_fills = order_fill_tuples;
_security_fills = security_fill_tuples;
void _update_fills(TableUpdate<FillImmutable, string> fills) {
using (_write()) {
Dictionary<FillTupleKey, FillTuple> removed = new Dictionary<FillTupleKey, FillTuple>(), added = new Dictionary<FillTupleKey, FillTuple>();
foreach (FillImmutable fill in fills.removed) {
FillTuple old_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
removed.Add(old_ft.key, old_ft);
_security_fills.remove(fill.SecurityKey, fill.FillKey);
_order_fills.remove(fill.OrderKey, fill.FillKey);
}
foreach (FillImmutable fill in fills.added) {
FillTuple new_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
added.Add(new_ft.key, new_ft);
_security_fills.add(fill.SecurityKey, fill.FillKey);
_order_fills.add(fill.OrderKey, fill.FillKey);
}
_fills_cache = fills.state;
_apply_changes(added, removed);
}
}
public TableUpdateTransform<FillTuple, string> fill_tuples => _fill_tuples;
public State update_fills(TableUpdate<FillImmutable, string> fills) {
var removed = fills.removed.Select(get_fill_tuple).ToList();
var added = fills.added.Select(get_fill_tuple).ToList();
var order_fills = _order_fills.clone();
var security_fills = _security_fills.clone();
HashSet<string> added_keys = new HashSet<string>(fills.added.Select(f => f.FillKey)),
removed_keys = new HashSet<string>(fills.removed.Select(f => f.FillKey));
foreach (var ft in removed) {
string k = ft.fill.FillKey;
if (!added_keys.Contains(k)) {
order_fills.remove(ft.fill.OrderKey, k);
security_fills.remove(ft.fill.SecurityKey, k);
void _update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
using (_write()) {
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
HashSet<uint> changed_keys = new HashSet<uint>();
foreach (var o in orders.removed) {
if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD? sec_md = _get_security_md(o.security_key());
foreach (FillImmutable fill in fills) {
FillTuple old_ft = new FillTuple(fill, o, sec_md);
removed.Add(old_ft.key, old_ft);
}
OrderRecordImmutable? curr_order = null;
if (orders.try_get(o.Key, out OrderRecordImmutable newval)) {
curr_order = newval;
changed_keys.Add(o.Key);
}
foreach (FillImmutable fill in fills) {
FillTuple new_ft = new FillTuple(fill, curr_order, sec_md);
added.Add(new_ft.key, new_ft);
}
}
}
foreach (var ft in added) {
string k = ft.fill.FillKey;
if (!removed_keys.Contains(k)) {
order_fills.add(ft.fill.OrderKey, k);
security_fills.add(ft.fill.SecurityKey, k);
foreach (var o in orders.added) {
if (!changed_keys.Contains(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD? sec_md = _get_security_md(o.security_key());
foreach (FillImmutable fill in fills) {
FillTuple old_ft = new FillTuple(fill, null, sec_md);
removed.Add(old_ft.key, old_ft);
FillTuple new_ft = new FillTuple(fill, o, sec_md);
added.Add(new_ft.key, new_ft);
}
}
}
return new State(fills.state, _orders, _securities, _fill_tuples.update(removed, added, f => f.key), order_fills, security_fills);
//
FillTuple get_fill_tuple(FillImmutable fill) =>
new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(),
_securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
_orders_cache = orders.state;
_apply_changes(added, removed);
}
}
public State update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
orders.detect_changed_keys(o => o.Key, out HashSet<uint> removed_keys, out HashSet<uint> added_keys, out HashSet<uint> changed_keys);
foreach (var k in removed_keys) {
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
added.AddRange(get_fill_tuples(null, fill_keys));
}
}
foreach (var k in added_keys) {
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(null, fill_keys));
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
}
}
foreach (var k in changed_keys) {
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
}
}
return new State(_fills, orders.state, _securities, _fill_tuples.update(removed, added, ft => ft.key), _order_fills, _security_fills);
//
IEnumerable<FillTuple> get_fill_tuples(OrderRecordImmutable? o, ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return new FillTuple(fill, o, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
});
}
public State update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
_securities.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
void _update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
using (_write()) {
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
_security_md_cache.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
foreach (var k in removed_keys) {
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(_securities[k].security, fill_keys));
added.AddRange(get_fill_tuples(null, fill_keys));
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD old_sec_md = _security_md_cache[k];
foreach (FillImmutable fill in fills) {
OrderRecordImmutable? order = _get_order(fill.OrderKey);
FillTuple old_ft = new FillTuple(fill, order, old_sec_md);
removed.Add(old_ft.key, old_ft);
FillTuple new_ft = new FillTuple(fill, order, null);
added.Add(new_ft.key, new_ft);
}
}
}
foreach (var k in added_keys) {
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(null, fill_keys));
added.AddRange(get_fill_tuples(securities[k].security, fill_keys));
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD new_sec_md = securities[k];
foreach (FillImmutable fill in fills) {
OrderRecordImmutable? order = _get_order(fill.OrderKey);
FillTuple old_ft = new FillTuple(fill, order, null);
removed.Add(old_ft.key, old_ft);
FillTuple new_ft = new FillTuple(fill, order, new_sec_md);
added.Add(new_ft.key, new_ft);
}
}
}
return new State(_fills, _orders, securities, _fill_tuples.update(removed, added, f => f.key), _order_fills, _security_fills);
//
IEnumerable<FillTuple> get_fill_tuples(SecurityImmutable? s, ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(), s);
});
_security_md_cache = securities;
_apply_changes(added, removed);
}
}
SecurityMD? _get_security_md(uint key) =>
_security_md_cache.TryGetValue(key, out SecurityMD smd) ? smd : new SecurityMD?();
OrderRecordImmutable? _get_order(uint key) =>
_orders_cache.TryGetValue(key, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?();
void _apply_changes(Dictionary<FillTupleKey, FillTuple> added, Dictionary<FillTupleKey, FillTuple> removed) {
foreach (var kv in removed)
_state.Remove(kv.Key);
foreach (var kv in added)
_state.Add(kv.Key, kv.Value);
_subject.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(added, removed));
}
IDisposable _read() {
while (_n_write_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_read_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
}
IDisposable _write() {
while (_n_read_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_write_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
}
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ namespace mbts.spark {
set_price_command = new DelegateCommand<double>(set_price);
_dispose = Disposable.Create(
size_calculator = new CalculatorLogic(0, calculator_buttons),
props_source.ObserveOnDispatcher().Subscribe(p => on_props(p.price, p.size))
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
);
void on_props(InlineUserProperty<double> price_prop, InlineUserProperty<uint> size_prop) {
@@ -114,7 +114,7 @@ namespace bts.spark {
(var security, var spec) = get_security(neworder.Def.SecurityKey);
//
// description
var mod_desc = modified_description(oldorder, neworder, spec.Value);
var mod_desc = modified_description(oldorder, neworder, spec);
if (!string.IsNullOrEmpty(mod_desc)) {
log_status(ActivityLogCategory.modify, security, mod_desc);
}
@@ -20,6 +20,10 @@ namespace mbts.ui.spark {
public DataTemplate StopLimitOrderTemplate { get; set; }
public DataTemplate StopMarketOrderTemplate { get; set; }
public DataTemplate TimeBoxOrderTemplate { get; set; }
public DataTemplate TickOrderTemplate { get; set; }
public DataTemplate LeanOrderTemplate { get; set; }
public DataTemplate JoinOrderTemplate { get; set; }
public DataTemplate TopOrderTemplate { get; set; }
public DataTemplate IcebergOrderTemplate { get; set; }
public DataTemplate SpreaderTemplate { get; set; }
public DataTemplate BTSStopOrderTemplate { get; set; }
@@ -33,6 +37,10 @@ namespace mbts.ui.spark {
case CustomOrderDefinition.CustomOneofCase.Stop: return StopMarketOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.StopLimit: return StopLimitOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.TimeBox: return TimeBoxOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Tick: return TickOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Lean: return LeanOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Join: return JoinOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Top: return TopOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
}
@@ -46,7 +46,7 @@ namespace mbts.spark {
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityImmutable> security_source, IObservable<InlineOrderProperties> order_properties) {
_model = model;
_security = backing(nameof(security), security_source.ObserveOnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
_security = backing(nameof(security), security_source.OnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
// guarentee that "this.security" will always be set before observers are notified
// this is important bc some observers will reference "this.security" in their callbacks
// - namely the [SelectorProperty]order_type's "get_default" function,
@@ -44,9 +44,9 @@ namespace mbts.spark {
: base(model) {
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override) {
_order_types.SetItems(p.child_order_types.value);
@@ -0,0 +1,287 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineJoinOrderProperties
{
public InlineJoinOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
var hedgetype = CustomOneofCase.Lean;
if(!child_order_types.Contains(hedgetype))
hedgetype = CustomOneofCase.Limit;
//ExceptionAssert.InvalidOperation.That(child_order_types.Contains(hedgetype), $"Hedge order type {hedgetype} is not in hedge_order_types");
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
join_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
enable_hedge = new InlineUserProperty<bool>(false, false);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> join_threshold;
public readonly InlineUserProperty<bool> enable_hedge;
public readonly InlineUserProperty<int> hedge_ticks;
}
class JoinOrderVM : OrderTypeVMBase<JoinOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineJoinOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
public uint tick_offset_on_deploy { get; set; } = 0;
public bts.spark.QuantityMeasure join_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
bool _enable_hedge = false;
public bool enable_hedge
{
get { return _enable_hedge; }
set
{
_enable_hedge = value;
RaisePropertyChanged(nameof(enable_hedge));
}
}
public int hedge_ticks { get; set; } = 1;
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineJoinOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineJoinOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
}
if (p.tick_offset_on_deploy.force_override)
{
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
RaisePropertyChanged(nameof(tick_offset_on_deploy));
}
if (p.join_threshold.force_override)
{
join_threshold = p.join_threshold.value;
RaisePropertyChanged(nameof(join_threshold));
}
if (p.enable_hedge.force_override)
{
enable_hedge = p.enable_hedge.value;
RaisePropertyChanged(nameof(enable_hedge));
}
if (p.hedge_ticks.force_override)
{
hedge_ticks = p.hedge_ticks.value;
RaisePropertyChanged(nameof(hedge_ticks));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
if (_hedge_order == null && enable_hedge)
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.join_order();
i.TickOffsetOnDeploy = tick_offset_on_deploy;
i.JoinThreshold = join_threshold;
i.EnableHedge = enable_hedge;
i.HedgeTicks = hedge_ticks;
child_order.assign_to(i.Child);
if(enable_hedge && hedge_order != null)
hedge_order.assign_to(i.Hedge);
value.Join = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public ICollection<IOrderTypeVM> hedge_order_types
{
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _hedge_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public IOrderTypeVM hedge_order
{
get
{
init();
return _hedge_order;
}
set
{
Disposable.dispose(ref _hedge_order_errors_sub);
_hedge_order = value;
_hedge_order?.init();
if (_hedge_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(hedge_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
if (_hedge_order_choices != null)
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
if (_hedge_order == null && _security.HasValue && enable_hedge)
_select_hedge_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
void _select_hedge_order(CustomOneofCase ot)
{
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_hedge_order == null)
_hedge_order = hedge_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(hedge_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
if ( (_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
set_error(nameof(hedge_order), string.Empty, true);
else clear_error(nameof(hedge_order));
}
}
}
}
@@ -0,0 +1,174 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineLeanOrderProperties
{
public InlineLeanOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
max_chase_ticks = new InlineUserProperty<uint>(10, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
public readonly InlineUserProperty<uint> max_chase_ticks;
}
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineLeanOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
public uint max_chase_ticks { get; set; } = 10;
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineLeanOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineLeanOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (p.cross_threshold.force_override)
{
cross_threshold = p.cross_threshold.value;
RaisePropertyChanged(nameof(cross_threshold));
}
if (p.max_chase_ticks.force_override)
{
max_chase_ticks = p.max_chase_ticks.value;
RaisePropertyChanged(nameof(max_chase_ticks));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.lean_order();
i.CrossThreshold = cross_threshold;
i.MaxChaseTicks = max_chase_ticks;
child_order.assign_to(i.Child);
value.Lean = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
}
}
}
}
@@ -14,6 +14,10 @@ namespace mbts.spark {
public static IOrderTypeVM stop(OrdersModel model) => new StopOrderVM(model);
public static IOrderTypeVM stop_limit(OrdersModel model) => new StopLimitOrderVM(model);
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TimeBoxOrderVM(model, security_source);
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TickOrderVM(model, security_source);
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityImmutable> security_source) => new LeanOrderVM(model, security_source);
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityImmutable> security_source) => new JoinOrderVM(model, security_source);
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TopOrderVM(model, security_source);
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityImmutable> security_source) => new IcebergOrderVM(model, security_source);
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source) => new SpreaderOrderVM(model, security_source);
@@ -27,6 +31,10 @@ namespace mbts.spark {
case CustomOneofCase.Stop: return stop(model);
case CustomOneofCase.StopLimit: return stop_limit(model);
case CustomOneofCase.TimeBox: return time_box(model, security_source);
case CustomOneofCase.Tick: return tick(model, security_source);
case CustomOneofCase.Lean: return lean(model, security_source);
case CustomOneofCase.Join: return join(model, security_source);
case CustomOneofCase.Top: return top(model, security_source);
case CustomOneofCase.Iceberg: return iceberg(model, security_source);
case CustomOneofCase.Spreader: return spreader(model, security_source);
default: throw new NotImplementedException();
@@ -1,15 +1,17 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
private class SpreaderOrderVM : OrderTypeVMBase<SpreaderOrderDefinition>, IOrderTypeVM, IDisposable {
readonly Property<SpreaderOrderDefinitionImmutable?> _template;
readonly StrategyEditorWrapper _strategy_editor_wrapper;
IDisposable _dispose;
SyntheticEditorVM _strategy_editor;
public SpreaderOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.SelectMany(s => model.spreader.security_strategy(s.Id)
@@ -26,15 +28,15 @@ namespace mbts.spark {
IObservable<InlineUserProperty<SpreaderOrderDefinitionImmutable>> order_template_source)
: base(model) {
_template = backing(nameof(template), new SpreaderOrderDefinitionImmutable?());
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
_dispose = Disposable.Create(
_template.ObserveOnDispatcher().Subscribe(vals => {
_strategy_editor_wrapper,
_template.OnDispatcher().Subscribe(vals => {
_validate();
}),
order_template_source.ObserveOnDispatcher().Subscribe(prop => {
order_template_source.OnDispatcher().Subscribe(prop => {
if (prop.force_override || (!template.HasValue && prop.has_value)) {
_template.Value = prop.value;
if (_strategy_editor != null)
_strategy_editor.set_synthetic(prop.value.Security, prop.value.ExecutionStrategy);
_validate();
}
})
@@ -43,23 +45,14 @@ namespace mbts.spark {
}
public SpreaderOrderDefinitionImmutable? template => _template.Value;
public SyntheticEditorVM strategy_editor {
get {
if (_strategy_editor == null && template.HasValue) {
var def = template.Value;
_strategy_editor = new SyntheticEditorVM(model.spreader, def.Security, def.ExecutionStrategy, model.security_has_working_orders(def.Security.Id));
}
return _strategy_editor;
}
}
public SyntheticEditorVM strategy_editor => _strategy_editor_wrapper.vm;
public IEnumerable<DialogSkirtItem> skirt_items => _strategy_editor_wrapper.skirt_items;
public override bool editable_properties_open {
get => base.editable_properties_open;
set {
if (!value && template.HasValue && _strategy_editor != null) {
if (!value && template.HasValue && strategy_editor != null) {
var mutable_template = template.Value.CloneToMutable();
mutable_template.ExecutionStrategy = _strategy_editor.get_strategy().CloneToMutable();
mutable_template.ExecutionStrategy = _strategy_editor_wrapper.vm.get_strategy().CloneToMutable();
_template.Value = new SpreaderOrderDefinitionImmutable(mutable_template);
}
base.editable_properties_open = value;
@@ -80,6 +73,39 @@ namespace mbts.spark {
set_error(nameof(template), "Waiting for security and/or strategy.", true);
else clear_error(nameof(template));
}
// lazily initializes SyntheticEditorVM and associated dialog skirt items
class StrategyEditorWrapper : IDisposable {
readonly SpreaderOrderVM _owner;
SyntheticEditorVM _vm;
DialogSkirtItem _save_strategy_item;
public StrategyEditorWrapper(SpreaderOrderVM owner) {
_owner = owner;
_owner._template.Changed += _order_template_changed;
}
public SyntheticEditorVM vm { get { _init(); return _vm; } }
public IEnumerable<DialogSkirtItem> skirt_items { get { _init(); yield return _save_strategy_item; } }
public void Dispose() {
Disposable.dispose(ref _vm);
_owner._template.Changed -= _order_template_changed;
}
void _init() {
if (_vm == null && _owner.template.HasValue) {
var def = _owner.template.Value;
_vm = new SyntheticEditorVM(_owner.model.spreader, def.Security, def.ExecutionStrategy, _owner.model.security_has_working_orders(def.Security.Id));
_save_strategy_item = new DialogSkirtItem("Save Strategy", _vm.commit);
}
}
void _order_template_changed(SpreaderOrderDefinitionImmutable? oldval, SpreaderOrderDefinitionImmutable? newval) {
if (_vm != null && newval.HasValue)
_vm.set_synthetic(newval.Value.Security, newval.Value.ExecutionStrategy);
}
}
}
}
}
@@ -0,0 +1,201 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineTickOrderProperties
{
public InlineTickOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
cross_ticks = new InlineUserProperty<uint>(0, false);
max_chase_ticks = new InlineUserProperty<uint>(10, false);
detach_on_toward = new InlineUserProperty<bool>(true, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
public readonly InlineUserProperty<uint> cross_ticks;
public readonly InlineUserProperty<uint> max_chase_ticks;
public readonly InlineUserProperty<bool> detach_on_toward;
}
class TickOrderVM : OrderTypeVMBase<TickOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineTickOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
public uint tick_offset_on_deploy { get; set; } = 0;
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
public uint cross_ticks { get; set; } = 0;
public uint max_chase_ticks { get; set; } = 10;
public bool detach_on_toward { get; set; } = true;
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineTickOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineTickOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (p.tick_offset_on_deploy.force_override)
{
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
RaisePropertyChanged(nameof(tick_offset_on_deploy));
}
if (p.cross_threshold.force_override)
{
cross_threshold = p.cross_threshold.value;
RaisePropertyChanged(nameof(cross_threshold));
}
if (p.cross_ticks.force_override)
{
cross_ticks = p.cross_ticks.value;
RaisePropertyChanged(nameof(cross_ticks));
}
if (p.max_chase_ticks.force_override)
{
max_chase_ticks = p.max_chase_ticks.value;
RaisePropertyChanged(nameof(max_chase_ticks));
}
if (p.detach_on_toward.force_override)
{
detach_on_toward = p.detach_on_toward.value;
RaisePropertyChanged(nameof(detach_on_toward));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.tick_order();
i.TickOffsetOnDeploy = tick_offset_on_deploy;
i.CrossThreshold = cross_threshold;
i.CrossTicks = cross_ticks;
i.MaxChaseTicks = max_chase_ticks;
i.DetachOnToward = detach_on_toward;
child_order.assign_to(i.Child);
value.Tick = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
}
}
}
}
@@ -50,9 +50,9 @@ namespace mbts.spark {
: base(model) {
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override) {
_order_types.SetItems(p.child_order_types.value);
@@ -0,0 +1,287 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineTopOrderProperties
{
public InlineTopOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
var hedgetype = CustomOneofCase.Lean;
if(!child_order_types.Contains(hedgetype))
hedgetype = CustomOneofCase.Limit;
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
watch_level_tick_offset = new InlineUserProperty<uint>(0, false);
top_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 }, false);
enable_hedge = new InlineUserProperty<bool>(false, false);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<uint> watch_level_tick_offset;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> top_threshold;
public readonly InlineUserProperty<bool> enable_hedge;
public readonly InlineUserProperty<int> hedge_ticks;
}
class TopOrderVM : OrderTypeVMBase<TopOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineTopOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
public uint watch_level_tick_offset { get; set; } = 0;
public bts.spark.QuantityMeasure top_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
bool _enable_hedge = false;
public bool enable_hedge
{
get { return _enable_hedge; }
set
{
_enable_hedge = value;
RaisePropertyChanged(nameof(enable_hedge));
}
}
public int hedge_ticks { get; set; } = 1;
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineTopOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList())))
{ }
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineTopOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.OnDispatcher()
.Subscribe(p =>
{
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
}
if (p.watch_level_tick_offset.force_override)
{
watch_level_tick_offset = p.watch_level_tick_offset.value;
RaisePropertyChanged(nameof(watch_level_tick_offset));
}
if (p.top_threshold.force_override)
{
top_threshold = p.top_threshold.value;
RaisePropertyChanged(nameof(top_threshold));
}
if (p.enable_hedge.force_override)
{
enable_hedge = p.enable_hedge.value;
RaisePropertyChanged(nameof(enable_hedge));
}
if (p.hedge_ticks.force_override)
{
hedge_ticks = p.hedge_ticks.value;
RaisePropertyChanged(nameof(hedge_ticks));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
if (_hedge_order == null && enable_hedge)
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.top_order();
i.WatchLevelTickOffset = watch_level_tick_offset;
i.TopThreshold = top_threshold;
i.EnableHedge = enable_hedge;
i.HedgeTicks = hedge_ticks;
child_order.assign_to(i.Child);
if (enable_hedge && hedge_order != null)
hedge_order.assign_to(i.Hedge);
value.Top = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public ICollection<IOrderTypeVM> hedge_order_types
{
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _hedge_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public IOrderTypeVM hedge_order
{
get
{
init();
return _hedge_order;
}
set
{
Disposable.dispose(ref _hedge_order_errors_sub);
_hedge_order = value;
_hedge_order?.init();
if (_hedge_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(hedge_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
if (_hedge_order_choices != null)
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
if (_hedge_order == null && _security.HasValue && enable_hedge)
_select_hedge_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
void _select_hedge_order(CustomOneofCase ot)
{
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_hedge_order == null)
_hedge_order = hedge_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(hedge_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
if ((_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
set_error(nameof(hedge_order), string.Empty, true);
else clear_error(nameof(hedge_order));
}
}
}
}
+3 -3
View File
@@ -31,11 +31,11 @@ namespace mbts.spark {
_service = service;
_spreader = spreader;
_error = error;
on_orders = _service.subscribei_orders().Table().Replay(1).RefCount();
on_orders = _service.subscribei_orders().Table().Cache();
IReadOnlyDictionary<uint, OrderRecordImmutable> empty_orders = new Dictionary<uint, OrderRecordImmutable>();
working_orders =
on_orders.Scan(empty_orders, (worders, table) => _update_working_orders(worders, table)).Replay(1).RefCount();
on_fills = _service.subscribei_fills().Table().Replay(1).RefCount();
on_fills = _service.subscribei_fills().Table().Cache();
_activity = new OrderActivitySummarizer(bus, on_orders);
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
@@ -53,7 +53,7 @@ namespace mbts.spark {
public SpreaderModel spreader => _spreader;
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
public IObservable<ITableUpdateTransform<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
public IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
+1 -1
View File
@@ -15,7 +15,7 @@
<OutputType>WinExe</OutputType>
<StartupObject>mbts.ui.shell.App</StartupObject>
<RootNamespace>bts</RootNamespace>
<LangVersion>7.1</LangVersion>
<LangVersion>7.2</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<Version>1.0.0</Version>
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
var thread_factory = new ShellThreadFactory();
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
Assert.IsNotNull(Application.Current);
Assert.IsNotNull(Application.Current.Windows);
Assert.AreEqual(Application.Current.Windows.Count, 0);
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
var thread_factory = new ShellThreadFactory();
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
Assert.AreEqual(Application.Current.Windows.Count, 0);
// an ISlowWindow gets its own thread
+2 -2
View File
@@ -175,8 +175,8 @@ namespace bts.spark
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
_tag = tag;
_subs = Disposable.Create(
parameter.ObserveOnDispatcher().Subscribe(p => _parameter = p),
can_execute.ObserveOnDispatcher().Subscribe(ce => {
parameter.OnDispatcher().Subscribe(p => _parameter = p),
can_execute.OnDispatcher().Subscribe(ce => {
_can_execute = ce;
raiseCanExecuteChanged();
})
+3
View File
@@ -5,6 +5,7 @@ using bts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using static mbts.spark.SparkBusData;
namespace mbts.spark
@@ -34,6 +35,8 @@ namespace mbts.spark
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
@@ -199,10 +199,10 @@ namespace mbts.ui {
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
// Order Types
public static BTSColor tick_order { get { return new BTSColor(31, 4, 57);}}
public static BTSColor lean_order { get { return new BTSColor(110, 77, 143);}}
public static BTSColor join_order { get { return new BTSColor(15, 1, 29);}}
public static BTSColor leader_order { get { return new BTSColor(40, 9, 72);}}
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color();}}
public static BTSColor lean_order { get { return BTSColors.purple.rgb_color();}}
public static BTSColor join_order { get { return BTSColors.purple.rgb_color();}}
public static BTSColor top_order { get { return BTSColors.purple.rgb_color();}}
}
public static class AW2 {
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
return rv;
}
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
yield return item;
}
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
// yield return item;
//}
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
TAccumulate a = seed;
@@ -345,6 +345,17 @@ namespace System.Reactive.Linq
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
}
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
}
}
}
@@ -54,5 +54,33 @@
rv |= try_convert(args[1], out arg2);
return rv;
}
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
if (args == null || args.Length != 3) {
arg1 = default;
arg2 = default;
arg3 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
return rv;
}
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
if (args == null || args.Length != 4) {
arg1 = default;
arg2 = default;
arg3 = default;
arg4 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
rv |= try_convert(args[3], out arg4);
return rv;
}
}
}
@@ -69,16 +69,18 @@ namespace mbts.ui.settings
if (window == null || window.Owner == null) return null;
MemoryStream out_stream = new MemoryStream();
window.GetState(out_stream);
var pos = _interop.GetPosition(window);
var state = new WindowState() {
owner = window.Owner.key(),
app_data = out_stream.ToArray(),
position = _interop.GetPosition(window),
title = window.Title
position = pos,
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
};
return state;
}
catch (Exception e) {
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
}
return null;
}
@@ -12,6 +12,9 @@ using bts.api.persistence;
namespace mbts.ui.settings
{
//
// This class is used for tools like SettingsManager.exe
//
[Export(typeof(IUserPersistFactory))]
class UISettingsFactory : IUserPersistFactory
{
@@ -34,7 +37,7 @@ namespace mbts.ui.settings
public IUserPersist get_persist_for(string name) {
var user = _users.Result.FirstOrDefault(u => u.Login == name);
return new UserUISettings(user.Id, _persist);
return new UserUISettings(user.Id, _persist, null);
}
}
}
@@ -39,6 +39,9 @@ namespace mbts.ui.shell {
[ProtoMember(102), DefaultValue(true)]
public bool always_on_top = true;
//[ProtoMember(103)] //used
[ProtoMember(104)]
public string custom_title;
}
[ProtoContract]
@@ -58,7 +61,7 @@ namespace mbts.ui.shell {
public WindowPosition position;
[ProtoMember(9)]
public string title;
public string title; // for debugging
}
[ProtoContract]
@@ -23,6 +23,9 @@ namespace mbts.ui
Task<IShellWindow> create(Stream load_stream);
}
// ILazyModule may also impement IShellMenu to get opening/closing notification
public interface ILazyMenuModule : IShellModule { }
public interface IPersistShellModule : IShellModule {
// Called at startup before the UI is shown
// This is an opportunity for you to kick off background services
@@ -17,5 +17,5 @@ namespace mbts.ui
WindowPosition GetPosition(IShellWindow window);
bool IsVisible(IShellWindow window);
void PinWindow(IShellWindow window);
}
}
}
@@ -10,6 +10,7 @@ using bts.api.core;
using bts.api.persistence;
using System.Text.RegularExpressions;
using System.Reactive.Linq;
using bts.spark;
namespace mbts.persist
{
@@ -18,11 +19,13 @@ namespace mbts.persist
protected abstract string user();
string uns(string ns) { return $"{user()}#{ns}"; }
PersistenceService _persist;
ErrorReporter _errors;
private const string _settings_change_base = "/ui/settingschange/";
public UISettingsBase(PersistenceService persist) {
public UISettingsBase(PersistenceService persist, ErrorReporter errors) {
_persist = persist;
_errors = errors;
}
@@ -50,7 +53,7 @@ namespace mbts.persist
Ns = uns(ns),
Key = { key }
});
check_error(ret.value.Result, ns, key, out bool empty);
check_error(ret, ns, key, out bool empty);
if (empty) return default(T);
return deserialize<T>(ret.value.Record.Single().Value);
}
@@ -67,20 +70,28 @@ namespace mbts.persist
return ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(stream, ProtoBuf.PrefixStyle.Fixed32);
}
private void check_error(RetrieveResponse.Types.Result result, string ns, string key, out bool empty) {
switch (result) {
case RetrieveResponse.Types.Result.EmptyNs:
case RetrieveResponse.Types.Result.NoSuchNs:
case RetrieveResponse.Types.Result.NoSuchKey:
empty = true;
return;
case RetrieveResponse.Types.Result.EmptyKey:
case RetrieveResponse.Types.Result.DuplicateKey:
throw new ArgumentException($"Invalid Key {result} '{key}'");
default:
case RetrieveResponse.Types.Result.Ok:
empty = false;
return;
private void check_error(Result<RetrieveResponse> result, string ns, string key, out bool empty) {
if (result.success) {
switch (result.value.Result) {
case RetrieveResponse.Types.Result.EmptyNs:
case RetrieveResponse.Types.Result.NoSuchNs:
case RetrieveResponse.Types.Result.NoSuchKey:
empty = true;
return;
case RetrieveResponse.Types.Result.EmptyKey:
case RetrieveResponse.Types.Result.DuplicateKey:
_errors?.report_error($"Invalid Key {result} '{key}'", "Requested Invalid Settings Key", System.Windows.MessageBoxImage.Information);
empty = true;
return;
default:
case RetrieveResponse.Types.Result.Ok:
empty = false;
return;
}
}
else {
_errors?.report_error($"There was an error communicating with the settings server: '{result.error_message}'", "Failed to retrieve settings from the server");
empty = true;
}
}
@@ -90,7 +101,7 @@ namespace mbts.persist
new RetrieveAllRequest() {
Ns = uns(ns)
});
check_error(results.value.Result, ns, "", out bool empty);
check_error(results, ns, "", out bool empty);
if (empty) return Enumerable.Empty<PersistResult<T>>();
return results.value.Record
.Where(r => re.IsMatch(r.Key))
@@ -131,7 +142,7 @@ namespace mbts.persist
protected override string user() { return "all_users"; }
[ImportingConstructor]
public UISettings([Import("settings")]PersistenceService p) : base(p) { }
public UISettings([Import("settings")]PersistenceService p, ErrorReporter err) : base(p,err) { }
}
[Export(typeof(IUserPersist))]
@@ -140,8 +151,8 @@ namespace mbts.persist
string _user;
[ImportingConstructor]
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist)
: base(persist) {
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
: base(persist,err) {
_user = $"User {user}";
}
@@ -3,6 +3,8 @@ using System.Collections.Generic;
namespace mbts.utils {
public static class EqualityComparer {
const int _hash_seed = 63607, _hash_multiplier = 6451;
public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals) =>
new AnonymousEqualityComparer<T>(equals);
@@ -28,6 +30,31 @@ namespace mbts.utils {
y != null &&
non_null_equals(x, y);
public static int aggregate_hash(int item1_hash, int item2_hash, int item3_hash) {
int rv = _hash_seed;
unchecked {
_accumulate_hash(ref rv, ref item1_hash);
_accumulate_hash(ref rv, ref item2_hash);
_accumulate_hash(ref rv, ref item3_hash);
}
return rv;
}
// Note: be wary of the performance cost this method may have, as frequently creating large arrays can be expensive.
// If you wish to avoid this cost, consider making an overload like the method above, with the appropriate number of item-hash arguments
public static int aggregate_hash(params int[] item_hashes) {
int hash = _hash_seed;
unchecked {
foreach (int item_hash in item_hashes)
hash = hash * _hash_multiplier + item_hash;
}
return hash;
}
// WARNING: be sure to call this within in an unchecked scope
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
aggregate_hash = aggregate_hash * _hash_multiplier + item_hash;
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
readonly Func<T, T, bool> _equals;
readonly Func<T, int> _get_hash_code;
@@ -2,9 +2,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
namespace mbts.utils {
interface IObservableDemuxer<TKey, in TInput, out TOutput> : IObserver<TInput> {
@@ -89,21 +89,17 @@ namespace mbts.utils {
_group_key = group_key;
}
public void OnCompleted() { }
public void OnError(Exception error) { }
public IObservable<ITableUpdateTransform<T, TKey>> subscribe(TGroupKey group_key) =>
Observable.Create<ITableUpdateTransform<T, TKey>>(o => {
var data = _map.GetOrAdd(group_key, gk => new Data());
lock (data) {
var current = data.current;
if (current.state.Count > 0) {
var state_clone = current.state.ToDictionary();
o.OnNext(new TableUpdateTransform<T, TKey>(state_clone, state_clone.Values.ToList(), new List<T>(0)));
}
using (data.read()) {
o.OnNext(data.current.as_snapshot());
return data.subject.Subscribe(o);
}
return data.subject.Subscribe(o);
});
public void OnCompleted() { }
public void OnError(Exception error) { }
public void OnNext(TableUpdate<T, TKey> table) {
var changes = new Dictionary<TGroupKey, ChangeSet>();
foreach (T value in table.added)
@@ -116,16 +112,29 @@ namespace mbts.utils {
}
Data _update(Data data, ChangeSet changes) {
lock (data) {
data.current = data.current.update(changes.removed, changes.added, _key);
data.subject.OnNext(data.current);
}
using (data.write())
data.subject.OnNext(data.current = data.current.update(changes.removed, changes.added, _key));
return data;
}
class Data {
int _n_read_ops, _n_write_ops;
public readonly Subject<TableUpdateTransform<T, TKey>> subject = new Subject<TableUpdateTransform<T, TKey>>();
public TableUpdateTransform<T, TKey> current = new TableUpdateTransform<T, TKey>();
public IDisposable read() {
while (_n_write_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_read_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
}
public IDisposable write() {
while (_n_read_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_write_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
}
}
// [John]I prefer this class over anonymous tuples bc it is easy to mix up Item1/Item2
@@ -7,12 +7,30 @@ using System.Linq;
using System.Reactive.Linq;
namespace mbts.utils {
interface IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
IReadOnlyDictionary<TKey, T> added { get; }
IReadOnlyDictionary<TKey, T> removed { get; }
}
interface ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
IReadOnlyDictionary<TKey, T> state { get; }
IReadOnlyList<T> added { get; }
IReadOnlyList<T> removed { get; }
}
class IncrementalUpdate<T, TKey> : IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
public IncrementalUpdate()
: this(new Dictionary<TKey, T>(0), new Dictionary<TKey, T>(0)) { }
public IncrementalUpdate(IReadOnlyDictionary<TKey, T> added, IReadOnlyDictionary<TKey, T> removed) {
this.added = added;
this.removed = removed;
}
public IReadOnlyDictionary<TKey, T> added { get; }
public IReadOnlyDictionary<TKey, T> removed { get; }
}
class TableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
readonly Dictionary<TKey, T> _state;
readonly IReadOnlyList<T> _added, _removed;
@@ -44,6 +62,18 @@ namespace mbts.utils {
table._state.Add(key(item), item);
return table;
}
public TableUpdateTransform<T, TKey> update(IIncrementalUpdate<T, TKey> update, Func<T, TKey> key) {
var table = new TableUpdateTransform<T, TKey>(new Dictionary<TKey, T>(_state), added, removed);
foreach (var k in update.removed.Keys)
table._state.Remove(k);
foreach (var kv in update.added)
table._state.Add(kv.Key, kv.Value);
return table;
}
internal TableUpdateTransform<T, TKey> as_snapshot() =>
new TableUpdateTransform<T, TKey>(_state, _state.Values.ToList(), new T[0]);
}
[Browsable(false)]
@@ -58,6 +88,15 @@ namespace mbts.utils {
.Select(u => new FilteredTableUpdateTransform<T, TKey>(u, predicate))
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
/// <summary>
/// Makes a 1:1 conversion to an IncrementalUpdate sequence of TResult (TKey remains the same).
/// </summary>
public static IObservable<IIncrementalUpdate<TResult, TKey>> convert<T, TKey, TResult>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector)
where TKey : IEquatable<TKey> =>
source.Select(update => new IncrementalUpdate<TResult, TKey>(
update.added.ToDictionary(kv => kv.Key, kv => selector(kv.Value)),
update.removed.ToDictionary(kv => kv.Key, kv => selector(kv.Value))));
/// <summary>
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
/// </summary>
@@ -69,6 +108,33 @@ namespace mbts.utils {
source.Scan(new PivotTableUpdateTransform<T, TKey, TResult, TResultKey>(selector, result_key, key),
(pivot, table) => pivot.update(table));
/// <summary>
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
/// </summary>
public static IObservable<IIncrementalUpdate<TResult, TResultKey>> with_respect_to<T, TKey, TResult, TResultKey>(
this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key)
where TKey : IEquatable<TKey>
where TResultKey : IEquatable<TResultKey> =>
//
source.Scan(new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(selector, result_key, key),
(pivot, table) => pivot.update(table));
/// <summary>
/// Uses a Scan operator to accumulate state in the resulting ITableUpdateTransform sequence.
/// </summary>
public static IObservable<ITableUpdateTransform<T, TKey>> table<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TKey> key) where TKey : IEquatable<TKey> =>
source.Scan(
new TableUpdateTransform<T, TKey>(),
(table, update) => table.update(update, key));
/// <summary>
/// Filters the incremental added/removed lists by a predicate. Observers are only notified when the filtered added/removed lists have elements.
/// </summary>
public static IObservable<IIncrementalUpdate<T, TKey>> filter<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, bool> predicate)
where TKey : IEquatable<TKey> => source
.Select(u => new IncrementalUpdate<T, TKey>(u.added.Where(kv => predicate(kv.Value)).ToDictionary(), u.removed.Where(kv => predicate(kv.Value)).ToDictionary()))
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
class FilteredTableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
internal FilteredTableUpdateTransform(ITableUpdateTransform<T, TKey> source, Func<T, bool> predicate) {
state = source.state;
@@ -81,6 +147,57 @@ namespace mbts.utils {
public IReadOnlyList<T> removed { get; }
}
class PivotIncrementalUpdate<T, TKey, TResult, TResultKey> : IIncrementalUpdate<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
readonly Dictionary<TResultKey, TResult> _state;
readonly SetMap<TResultKey, TKey> _key_map;
readonly Func<T, TResult> _selector;
readonly Func<TResult, TResultKey> _result_key;
readonly Func<T, TKey> _key;
internal PivotIncrementalUpdate(Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) :
this(new Dictionary<TResultKey, TResult>(), new Dictionary<TResultKey, TResult>(0), new Dictionary<TResultKey, TResult>(0), new SetMap<TResultKey, TKey>(), selector, result_key, key) { }
PivotIncrementalUpdate(Dictionary<TResultKey, TResult> state, IReadOnlyDictionary<TResultKey, TResult> added, IReadOnlyDictionary<TResultKey, TResult> removed,
SetMap<TResultKey, TKey> key_map, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) {
//
_state = state;
this.added = added;
this.removed = removed;
_key_map = key_map;
_selector = selector;
_result_key = result_key;
_key = key;
}
public IReadOnlyDictionary<TResultKey, TResult> state => _state;
public IReadOnlyDictionary<TResultKey, TResult> added { get; }
public IReadOnlyDictionary<TResultKey, TResult> removed { get; }
public PivotIncrementalUpdate<T, TKey, TResult, TResultKey> update(IIncrementalUpdate<T, TKey> table) {
Dictionary<TResultKey, TResult> removed = new Dictionary<TResultKey, TResult>(), added = new Dictionary<TResultKey, TResult>();
var state_clone = new Dictionary<TResultKey, TResult>(_state);
foreach (var kv in table.removed) {
var result = _selector(kv.Value);
var rkey = _result_key(result);
if (_key_map.removes_key(rkey, kv.Key))
removed.Add(rkey, result);
}
foreach (var kv in table.added) {
var result = _selector(kv.Value);
var rkey = _result_key(result);
if (_key_map.adds_key(rkey, kv.Key)) {
if (!removed.Remove(rkey))
added.Add(rkey, result);
}
}
foreach (var k in removed.Keys)
state_clone.Remove(k);
foreach (var kv in added)
state_clone.Add(kv.Key, kv.Value);
return new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(state_clone, added, removed, _key_map, _selector, _result_key, _key);
}
}
class PivotTableUpdateTransform<T, TKey, TResult, TResultKey> : ITableUpdateTransform<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
readonly Dictionary<TResultKey, TResult> _state;
readonly SetMap<TResultKey, TKey> _key_map;
@@ -471,7 +471,7 @@ namespace mbts.ui.shell
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new ObservablePropertySink<T>(this, name, default_value);
_observable_properties.Add(source.ObserveOnDispatcher().Subscribe(d => prop.set(select(d))));
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
return prop;
}
@@ -526,8 +526,8 @@ namespace mbts.ui.shell
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new SelectorProperty<T>(this, name, select_default, items_match, validation_steps);
_observable_properties.Add(items.ObserveOnDispatcher().Subscribe(prop));
_observable_properties.Add(desired_item.ObserveOnDispatcher().Subscribe(prop));
_observable_properties.Add(items.OnDispatcher().Subscribe(prop));
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(prop));
return prop;
}
+191 -112
View File
@@ -1,141 +1,220 @@
using mbts.spark;
using mbts.ui.extensions;
using bts.api;
using mbts.spark;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Data;
using System.Windows.Media;
using static mbts.config.Configuration;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
public class Badge: Control {
readonly static double _dpi = get<bts.config.System>().dpi_ratio;
public class Badge : Control {
public const double LINE_SPACING = 1;
// Storing the defaults here for readability
// Warning: these properties are used to calculate the ladder's row_height. Please change the values below if you want to change properties that affect the layout.
// ** - value is not the actual PropertyMetadata.DefaultValue
public static readonly Thickness
DefaultMargin = new Thickness(4, 2, 4, 2), // **
DefaultPadding = new Thickness(1),
DefaultBorderThickness = new Thickness(2);
public static readonly double
DefaultCornerRadius = 1,
DefaultFontSize = 10,
DefaultWidth = 63; // **
public static readonly FontFamily DefaultFontFamily = new FontFamily("Consolas");
readonly GlyphContentRect _gcr = new GlyphContentRect();
Brush _background, _foreground, _border_brush;
Typeface _face;
FormattedText _center_text;
FormattedText _top_text;
FormattedText _bottom_text;
static Badge() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(typeof(Badge)));
WidthProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(15.0));
HeightProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0));
FontSizeProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
ForegroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.White, update_text));
BackgroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
BorderThicknessProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new Thickness(1)));
BorderBrushProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Black));
FontFamilyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new FontFamily("Consolas"), FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
//TextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
//TopTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
//BottomTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
// MinWidth
WPFHelper.override_metadata<double, Badge>(
MinWidthProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.min_width = v,
0);
// Width
WPFHelper.override_metadata<double, Badge>(
WidthProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.width = v,
double.NaN);
// Max Width
WPFHelper.override_metadata<double, Badge>(
MaxWidthProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.max_width = v,
double.MaxValue);
// Min Height
WPFHelper.override_metadata<double, Badge>(
MinHeightProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.min_height = v,
0);
// Height
WPFHelper.override_metadata<double, Badge>(
HeightProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.height = v,
double.NaN);
// Max Height
WPFHelper.override_metadata<double, Badge>(
MaxHeightProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.max_height = v,
double.MaxValue);
// FontSize
WPFHelper.override_metadata<double, Badge>(
FontSizeProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.font_size = v,
DefaultFontSize);
// Foreground
WPFHelper.override_metadata<Brush, Badge>(
ForegroundProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._foreground = v,
Brushes.White);
// Background
WPFHelper.override_metadata<Brush, Badge>(
BackgroundProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._background = v,
Brushes.Transparent);
// BorderThickness
WPFHelper.override_metadata<Thickness, Badge>(
BorderThicknessProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.border_thickness = v,
DefaultBorderThickness);
// BorderBrush
WPFHelper.override_metadata<Brush, Badge>(
BorderBrushProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._border_brush = v,
Brushes.Black);
// FontFamily
WPFHelper.override_metadata<FontFamily, Badge>(
FontFamilyProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.glyph_context = b._gcr.glyph_context.with(v),
DefaultFontFamily);
// HoriontalAlignment
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
HorizontalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.horizontal_alignment = v,
HorizontalAlignment.Stretch);
// HoriontalContentAlignment
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
HorizontalContentAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.horizontal_content_alignment = v,
HorizontalAlignment.Center);
// VerticalAlignment
WPFHelper.override_metadata<VerticalAlignment, Badge>(
VerticalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.vertical_alignment = v,
VerticalAlignment.Center);
// VerticalContentAlignment
WPFHelper.override_metadata<VerticalAlignment, Badge>(
VerticalContentAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.vertical_content_alignment = v,
VerticalAlignment.Center);
// Padding
WPFHelper.override_metadata<Thickness, Badge>(
PaddingProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(p, v) => p._gcr.padding = v,
DefaultPadding);
}
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
format_text((b,v) => b._center_text= v)));
public string TopText {
get { return (string)GetValue(TopTextProperty); }
set { SetValue(TopTextProperty, value); }
}
public static readonly DependencyProperty TopTextProperty =
DependencyProperty.Register(nameof(TopText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
format_text((b, v) => b._top_text = v)));
public string BottomText {
get { return (string)GetValue(BottomTextProperty); }
set { SetValue(BottomTextProperty, value); }
public Badge() {
_gcr.min_width = MinWidth;
_gcr.width = Width;
_gcr.max_width = MaxWidth;
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
_gcr.vertical_alignment = VerticalAlignment;
_gcr.vertical_content_alignment = VerticalContentAlignment;
_background = Background;
_foreground = Foreground;
_border_brush = BorderBrush;
_gcr.font_size = FontSize;
_gcr.text_lines = TextLines;
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
}
public static readonly DependencyProperty BottomTextProperty =
DependencyProperty.Register(nameof(BottomText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
format_text((b,v)=>b._bottom_text=v)));
public static readonly DependencyProperty TextLinesProperty =
WPFHelper.create_dp<IReadOnlyList<string>, Badge>(
nameof(TextLines),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.text_lines = v,
new string[0]);
public IReadOnlyList<string> TextLines {
get => GetValue(TextLinesProperty) as IReadOnlyList<string>;
set => SetValue(TextLinesProperty, value);
}
public double CornerRadius {
get { return (double)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(double), typeof(Badge), new PropertyMetadata(3.0));
WPFHelper.create_dp<double, Badge>(nameof(CornerRadius), DefaultCornerRadius);
static PropertyChangedCallback format_text(Action<Badge,FormattedText> f) {
return (d, e) => {
Badge b = (Badge)d;
ensure_fontface(b);
string text = e.NewValue as string;
if (string.IsNullOrEmpty(text)) f(b, null);
f(b, new FormattedText(text,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
b._face, b.FontSize, b.Foreground, _dpi));
b.InvalidateVisual();
};
protected override Size ArrangeOverride(Size finalSize) => finalSize;
protected override Size MeasureOverride(Size availableSize) {
_gcr.available_size = availableSize;
return _gcr.layout_size;
}
static FormattedText format_text(Badge badge, DependencyProperty source) {
FormattedText result = null;
var value = badge.GetValue(source);
format_text((b, v) => result = v)(badge, new DependencyPropertyChangedEventArgs(source, value, value));
return result;
}
static void update_text(DependencyObject d, DependencyPropertyChangedEventArgs args) {
// Force formatted text regeneration when font/color changes
Badge badge = (Badge)d;
badge._face = null;
ensure_fontface(badge);
badge._center_text = format_text(badge, TextProperty);
badge._top_text = format_text(badge, TopTextProperty);
badge._bottom_text = format_text(badge, BottomTextProperty);
}
private static void ensure_fontface(Badge b) {
b._face = b._face ?? new Typeface(b.FontFamily, b.FontStyle, b.FontWeight, b.FontStretch);
}
//protected override Size MeasureOverride(Size availableSize) {
// Double calc_height = 0.0;
// Double calc_width = 0.0;
// Size size = new Size(0, 0);
// Size center_size = new Size(_center_text.Width, _center_text.Height);
// Size top_size = new Size(_top_text.Width, _top_text.Height);
// Size bottom_size = new Size(_bottom_text.Width, _bottom_text.Height);
// calc_width = Math.Max(center_size.Width, top_size.Width);
// calc_width = Math.Max(calc_width, bottom_size.Width);
// calc_height += center_size.Height;
// calc_height += top_size.Height;
// calc_height += bottom_size.Height;
// size.Width = calc_width;
// size.Height = calc_height;
// return size;
//}
protected override void OnRender(DrawingContext dc) {
base.OnRender(dc);
double border_thickness = BorderThickness.Top;
var pen = new Pen(BorderBrush, border_thickness);
dc.DrawRoundedRectangle(Background, pen, new Rect(0, 0 + border_thickness, ActualWidth, ActualHeight - 2 * border_thickness), CornerRadius, CornerRadius);
if (_center_text != null)
dc.DrawText(_center_text, new Point((ActualWidth-_center_text.Width)/2, (ActualHeight-_center_text.Height)/2));
if (_top_text != null)
dc.DrawText(_top_text, new Point(border_thickness, border_thickness));
if (_bottom_text != null)
dc.DrawText(_bottom_text, new Point(border_thickness, ActualHeight - _bottom_text.Height - border_thickness));
dc.DrawRoundedRectangle(Background, pen, new Rect(0, border_thickness, _gcr.layout_size.Width, _gcr.layout_size.Height), CornerRadius, CornerRadius);
var gr = _gcr.glyph_run;
if (gr != null)
dc.DrawGlyphRun(_foreground, gr);
}
}
}
class BadgeTextLinesConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (ObjectHelper.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
return BadgeRender.format(side, quantity, filled, style);
else return new string[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
}
@@ -73,7 +73,7 @@ namespace mbts.ui.controls {
Disposable.dispose(ref timer._trigger_sub);
IObservable<object> newval = args.NewValue as IObservable<object>;
if (newval != null) {
timer._trigger_sub = newval.ObserveOnDispatcher().Subscribe(timer.start);
timer._trigger_sub = newval.OnDispatcher().Subscribe(timer.start);
}
}
@@ -1,4 +1,5 @@
using mbts.spark.filters;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
@@ -60,13 +61,19 @@ namespace mbts.ui.controls {
WPFHelper.create_dp<ListSortDirection?, ColumnBase>(
nameof(SortDirection), (o, v) => o.OnSortDirectionChanged(v));
public ListSortDirection? SortDirection { get => GetValue(SortDirectionProperty) as ListSortDirection?; set => SetValue(SortDirectionProperty, value); }
public ListSortDirection? SortDirection {
get => GetValue(SortDirectionProperty) as ListSortDirection?;
set => SetValue(SortDirectionProperty, value);
}
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
public string sort_member_path { get; set; }
public double original_width { get; set; }
public bool is_collapsed { get; set; }
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) { }
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
sort_direction_changed?.Invoke(this, new_value);
}
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey> {
@@ -49,7 +49,7 @@ namespace mbts.spark.filters {
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer, comparer);
public static IDataGridColumnPersistentFilter<TRow, TState> Create<TRow, TValue, TState>(
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
Func<TRow, TValue> get_row_val,
Func<ISet<TValue>, TState> get_state,
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
@@ -115,14 +115,14 @@ namespace mbts.spark.filters {
IComparer<TValue> comparer = null
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
//
_items_sub = items.ObserveOnDispatcher().Subscribe(new_items => {
_items_sub = items.OnDispatcher().Subscribe(new_items => {
_items_set.SetItems(new_items.Concat(_selected_items_set));
_unsorted_items.sync_with(_items_set);
});
}
public DynamicItemsFilter(
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
Func<TRow, TValue> get_row_val,
Func<ISet<TValue>, TState> get_state,
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
@@ -131,7 +131,7 @@ namespace mbts.spark.filters {
IComparer<TValue> comparer = null
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
//
_items_sub = items.ObserveOnDispatcher().Subscribe(update => {
_items_sub = items.OnDispatcher().Subscribe(update => {
foreach (var v in update.added)
_items_set.Add(v);
foreach (var v in update.removed.Except(_selected_items_set))
@@ -182,7 +182,7 @@ namespace mbts.spark.filters {
protected override void _load_state_internal(TState state) {
Disposable.dispose(ref _desired_items_sub);
_desired_items_sub = _load_state(state).ObserveOnDispatcher().Subscribe(_on_desired_item);
_desired_items_sub = _load_state(state).OnDispatcher().Subscribe(_on_desired_item);
}
protected override void _apply() {
@@ -54,8 +54,9 @@ namespace mbts.ui {
public static TimeTextTuple FromTicks(long ticks) =>
FromDateTime(new DateTime(ticks));
public static TimeTextTuple FromDateTime(DateTime date_time) =>
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString("t"));
public static TimeTextTuple FromDateTime(DateTime date_time) => FromDateTime(date_time, "t");
public static TimeTextTuple FromDateTime(DateTime date_time, string time_format) =>
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString(time_format));
public TimeTextTuple(string month_text, string day_text, string year_text, string time_text) {
_month_text = month_text;
@@ -107,14 +108,23 @@ namespace mbts.ui {
static DateTime? _parse(string month_text, string day_text, string year_text, string time_text) {
DateTime? t = _parse(time_text);
TimeSpan? t = _parse_time(time_text);
if (t.HasValue) {
DateTime? d = _parse($"{month_text}/{day_text}/{year_text}");
if (d.HasValue) return d.Value.Date.AddDays(t.Value.TimeOfDay.TotalDays);
if (d.HasValue) return d.Value.Date.Add(t.Value);
}
return null;
}
static TimeSpan? _parse_time(string text) {
DateTime? dt = _parse(text);
if (dt.HasValue) {
return dt.Value.TimeOfDay;
} else if (TimeSpan.TryParse(text, out TimeSpan result) && result < TimeSpan.FromDays(1)) {
return result;
} else return null;
}
static DateTime? _parse(string text) {
if (!string.IsNullOrEmpty(text) && DateTime.TryParse(text, out DateTime result))
return result;
@@ -26,7 +26,7 @@
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding Mode, RelativeSource={RelativeSource TemplatedParent}}"
/>
<Grid x:Name="PeriodRoot" Grid.Row="1" Margin="0,6,0,0" IsEnabled="False">
<Grid x:Name="PeriodRoot" Grid.Row="1" Margin="24,6,0,0" IsEnabled="False">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="4"/>
@@ -73,8 +73,9 @@ namespace mbts.ui.controls {
public enum TimeFilterMode {
Anytime,
Today,
[Description("Period:")]
Period,
[Description("Glbx Open")]
[Description("Since Open")]
CMEOpen
}
}
@@ -10,6 +10,7 @@
<ResourceDictionary Source="/view/Controls/PriceEntryControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Time/TimeSpanControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/QuantityMeasureControl.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="uag:OrderDetailsPopupControl">
@@ -155,6 +156,186 @@
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.TimeBoxOrderTemplate>
<usp:OrderModificationTemplateSelector.TickOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Cross Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
<TextBlock Grid.Row="3" VerticalAlignment="Center">Cross Ticks</TextBlock>
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding cross_ticks}"/>
<TextBlock Grid.Row="5" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
<c:TickUpDownUInt Grid.Row="5" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
<CheckBox Grid.Row="7" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding detach_on_toward}">Detach on Toward</CheckBox>
<TextBlock Grid.Row="9" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<TextBlock Grid.Row="11">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="11" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.TickOrderTemplate>
<usp:OrderModificationTemplateSelector.LeanOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Cross Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
<TextBlock Grid.Row="3" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
<TextBlock Grid.Row="5">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="5" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.LeanOrderTemplate>
<usp:OrderModificationTemplateSelector.JoinOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Join Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding join_threshold}"/>
<CheckBox Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding enable_hedge}">Enable Hedge</CheckBox>
<TextBlock Grid.Row="5" VerticalAlignment="Center" IsEnabled="{Binding enable_hedge}" >Hedge Ticks</TextBlock>
<c:TickUpDownInt Grid.Row="5" Grid.Column="2" IsEnabled="{Binding enable_hedge}" Value="{Binding hedge_ticks}"/>
<TextBlock Grid.Row="7">Hedge</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="7" Grid.Column="2"
IsEnabled="{Binding enable_hedge}"
IsOpen="{Binding hedge_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding hedge_order_types}"
SelectedItem="{Binding hedge_order}"
HasEditableProperties="{Binding hedge_order.has_editable_properties, FallbackValue=False}"
/>
<TextBlock Grid.Row="9" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<TextBlock Grid.Row="11">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="11" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.JoinOrderTemplate>
<usp:OrderModificationTemplateSelector.TopOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Top Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
<CheckBox Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding enable_hedge}">Enable Hedge</CheckBox>
<TextBlock Grid.Row="5" VerticalAlignment="Center" IsEnabled="{Binding enable_hedge}" >Hedge Ticks</TextBlock>
<c:TickUpDownInt Grid.Row="5" Grid.Column="2" IsEnabled="{Binding enable_hedge}" Value="{Binding hedge_ticks}"/>
<TextBlock Grid.Row="7">Hedge</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="7" Grid.Column="2"
IsEnabled="{Binding enable_hedge}"
IsOpen="{Binding hedge_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding hedge_order_types}"
SelectedItem="{Binding hedge_order}"
HasEditableProperties="{Binding hedge_order.has_editable_properties, FallbackValue=False}"
/>
<TextBlock Grid.Row="9" VerticalAlignment="Center">Watch Levels</TextBlock>
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding watch_level_tick_offset}"/>
<TextBlock Grid.Row="11">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="11" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.TopOrderTemplate>
<usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
<DataTemplate>
<Grid>
@@ -180,21 +361,31 @@
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
<usp:OrderModificationTemplateSelector.SpreaderTemplate>
<DataTemplate>
<usp:ExecutionStrategyEditor
HorizontalAlignment="Stretch" Margin="6"
DataContext="{Binding strategy_editor}"
SyntheticName="{Binding name}"
IsNew="{Binding is_new}"
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
PriceCalc="{Binding price_calc}"
ParameterRows="{Binding rows}"
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<usp:ExecutionStrategyEditor
Margin="6"
HorizontalAlignment="Stretch"
DataContext="{Binding strategy_editor}"
SyntheticName="{Binding name}"
TickFactors="{Binding tick_factors}"
AvailableTickFactors="{Binding available_tick_factors_view}"
IsNew="False"
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
PriceCalc="{Binding price_calc}"
ParameterRows="{Binding rows}"
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
/>
<c:DialogSkirt Grid.Row="1" Margin="-4" RightItems="{Binding skirt_items}"/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.SpreaderTemplate>
</usp:OrderModificationTemplateSelector>
@@ -15,7 +15,7 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:Badge Background="{Binding Metadata, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeBrushConverter}}" Width="15" Height="10" HorizontalAlignment="Left" />
<c:Badge Background="{Binding Metadata, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeBrushConverter}}" BorderThickness="1" Width="15" Height="10" HorizontalAlignment="Left" />
<TextBlock Text="{Binding OrderType, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeDisplayTextConverter}}" Margin="20,0,0,0" HorizontalAlignment="Left" />
</Grid>
</Border>
@@ -28,7 +28,14 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TickDisplayTwoWayConverter}, Mode=TwoWay}"
/>
>
<TextBox.CommandBindings>
<CommandBinding Command="{x:Static u:TextBoxBehavior.SelectAllCommand}" Executed="{x:Static u:TextBoxBehavior.SelectAllCommandExecutedHandler}"/>
</TextBox.CommandBindings>
<TextBox.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static u:TextBoxBehavior.SelectAllCommand}"/>
</TextBox.InputBindings>
</TextBox>
</c:TickUpDown>
</ControlTemplate>
<Style TargetType="usp:PriceEntryControl">
@@ -47,24 +47,30 @@ namespace mbts.ui.spark {
#endregion
TextBox _text;
IDisposable _text_input_hook;
public PriceEntryControl() {
TickUpCommand = new DelegateCommand(_tick_up);
TickDownCommand = new DelegateCommand(_tick_down);
}
IDisposable _keyboard_focus_sub;
bool _has_keyboard_focus;
public override void OnApplyTemplate() {
Disposable.dispose(ref _keyboard_focus_sub);
_text = this.assert_template_child<TextBox>("_text");
Disposable.dispose(ref _text_input_hook);
_text_input_hook = _text.SubscribePreviewTextInput((s, e) => e.Handled = !RegexFilters.PriceInput.IsMatch(_text.PeekText(e)));
_keyboard_focus_sub = _text.SubscribeKeyboardFocus(val => _has_keyboard_focus = val);
_update_text_binding();
}
void _tick_up() {
if (_has_keyboard_focus) {
_text.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
double v = Value;
if (double.IsNaN(v)) SetCurrentValue(ValueProperty, TickDefaultValue);
else {
if (double.IsNaN(v)) {
SetCurrentValue(ValueProperty, TickDefaultValue);
} else {
var spec = ContractSpec;
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickup(v));
else SetCurrentValue(ValueProperty, TickDefaultValue);
@@ -72,9 +78,13 @@ namespace mbts.ui.spark {
}
void _tick_down() {
if (_has_keyboard_focus) {
_text.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
double v = Value;
if (double.IsNaN(v)) SetCurrentValue(ValueProperty, TickDefaultValue);
else {
if (double.IsNaN(v)) {
SetCurrentValue(ValueProperty, TickDefaultValue);
} else {
var spec = ContractSpec;
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickdown(v));
else SetCurrentValue(ValueProperty, TickDefaultValue);
@@ -10,9 +10,11 @@ namespace mbts.ui.controls {
static SparkMenu() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenu), new FrameworkPropertyMetadata(typeof(SparkMenu)));
public SparkMenu() =>
public SparkMenu() {
SetCurrentValue(FocusableProperty, false);
}
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
@@ -23,38 +23,42 @@ namespace mbts.ui.controls {
}
protected override void OnPreviewKeyDown(KeyEventArgs e) {
switch (Keyboard.Modifiers) {
case ModifierKeys.None: {
switch (e.Key) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownParameter);
e.Handled = true;
break;
if (EnableArrowKeyTick) {
switch (Keyboard.Modifiers) {
case ModifierKeys.None: {
switch (e.Key) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownParameter);
e.Handled = true;
break;
}
}
}
break;
case ModifierKeys.Alt: {
// see: https://stackoverflow.com/questions/3099472/previewkeydown-is-not-seeing-alt-modifiers
switch (e.SystemKey) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpAltParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownAltParameter);
e.Handled = true;
break;
break;
case ModifierKeys.Alt: {
// see: https://stackoverflow.com/questions/3099472/previewkeydown-is-not-seeing-alt-modifiers
switch (e.SystemKey) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpAltParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownAltParameter);
e.Handled = true;
break;
}
}
}
break;
break;
}
}
base.OnPreviewKeyDown(e);
}
public bool EnableArrowKeyTick { get; set; } = true;
#region TickCommand
public ICommand TickCommand {
get { return (ICommand)GetValue(TickCommandProperty); }
@@ -31,8 +31,14 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:TickUpDown x:Name="PART_time_tick" Grid.Column="0" MinWidth="85" HorizontalAlignment="Stretch">
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
<c:TickUpDown x:Name="PART_time_tick" Grid.Column="0" MinWidth="85" HorizontalAlignment="Stretch" EnableArrowKeyTick="False">
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True">
<TextBox.InputBindings>
<KeyBinding Key="Up" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int 1}"/>
<KeyBinding Key="Down" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int -1}"/>
<u:CustomInputBinding Gesture="{x:Static u:InputGestures.LeftTripleClick}" Command="{x:Static u:TextBoxBehavior.SelectAllCommand}"/>
</TextBox.InputBindings>
</TextBox>
</c:TickUpDown>
<c:TickUpDown x:Name="PART_month_tick" Grid.Column="2" Width="50">
<TextBox x:Name="PART_month_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
@@ -3,6 +3,7 @@ using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
@@ -14,7 +15,9 @@ namespace mbts.ui.controls {
/// Interaction logic for DateTimePicker.xaml
/// </summary>
public class DateTimePicker : Control {
TimeTupleVM _vm;
public static readonly RoutedCommand TickTimeComponentCommand = new RoutedCommand();
DTPController _controller;
bool _suspend_notify_vm;
static DateTimePicker() =>
@@ -24,8 +27,8 @@ namespace mbts.ui.controls {
new FrameworkPropertyMetadata(default(TimeTextTuple), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnTimeTupleChanged));
static void OnTimeTupleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var dtp = (DateTimePicker)d;
if (!dtp._suspend_notify_vm && dtp._vm != null)
dtp._vm.result = (TimeTextTuple)e.NewValue;
if (!dtp._suspend_notify_vm && dtp._controller != null)
dtp._controller.result = (TimeTextTuple)e.NewValue;
}
public TimeTextTuple TimeTuple { get => (TimeTextTuple)GetValue(TimeTupleProperty); set => SetValue(TimeTupleProperty, value); }
@@ -41,59 +44,74 @@ namespace mbts.ui.controls {
var time = this.assert_template_child<TickUpDown>("PART_time_tick");
var time_text = this.assert_template_child<TextBox>("PART_time_text");
if (_vm != null) _vm.PropertyChanged -= _vm_PropertyChanged;
_vm = new TimeTupleVM();
_vm.result = TimeTuple;
_vm.PropertyChanged += _vm_PropertyChanged;
if (_controller != null) _controller.PropertyChanged -= _vm_PropertyChanged;
_controller = new DTPController(time_text);
_controller.result = TimeTuple;
_controller.PropertyChanged += _vm_PropertyChanged;
time.CommandBindings.Add(new CommandBinding(TickTimeComponentCommand, (s, e) => {
_controller.tick_time_component((int)e.Parameter);
}));
time.CommandBindings.Add(new CommandBinding(TextBoxBehavior.SelectAllCommand, TextBoxBehavior.SelectAllCommandExecutedHandler));
const int delay = 500;
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _vm });
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _controller });
time.TickUpParameter = 1;
time.TickDownParameter = -1;
time.TickUpAltParameter = 60;
time.TickDownAltParameter = -60;
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _vm, Delay = delay, ValidatesOnExceptions = true });
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !MatchAny(time_text.PeekText(e), RegexFilters.Time.StandardTime, RegexFilters.Time.MilitaryTime)
));
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _controller, Delay = delay, ValidatesOnExceptions = true });
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) => {
string preview = time_text.PeekText(e);
if (RegexFilters.Time.StandardTimeOptionalSeconds.IsMatch(preview)) {
_controller.use_military_time = false;
} else if (RegexFilters.Time.MilitaryTimeOptionalSeconds.IsMatch(preview)) {
_controller.use_military_time = true;
} else {
e.Handled = true;
}
}));
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _vm });
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _controller });
month.TickUpParameter = 1;
month.TickDownParameter = -1;
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _vm, Delay = delay });
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _controller, Delay = delay });
_hooks.Add(month_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !RegexFilters.Time.MonthAbbrev.IsMatch(month_text.PeekText(e))
));
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _vm });
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _controller });
day.TickUpParameter = 1;
day.TickDownParameter = -1;
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _vm, Delay = delay });
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _controller, Delay = delay });
_hooks.Add(day_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !RegexFilters.Time.DaysInMonth.IsMatch(day_text.PeekText(e))
));
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _vm });
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _controller });
year.TickUpParameter = 1;
year.TickDownParameter = -1;
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _vm, Delay = delay });
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _controller, Delay = delay });
_hooks.Add(year_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !RegexFilters.Time.Year.IsMatch(year_text.PeekText(e))
));
}
void _vm_PropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(TimeTupleVM.result)) {
if (e.PropertyName == nameof(DTPController.result)) {
_suspend_notify_vm = true;
TimeTuple = _vm.result;
TimeTuple = _controller.result;
_suspend_notify_vm = false;
}
}
private class TimeTupleVM : ViewModelBase {
// This is not exactly a view-model, bc it accesses the time TextBox directly so it can get/set the CaretIndex,
// which is used to determine which time-componenet - hh:mm:ss AM/PM - should be incremented/decremented via the up/down arrow-keys.
class DTPController : ViewModelBase {
readonly TextBox _time_text_box;
TimeTextTuple _result;
public TimeTupleVM() {
public DTPController(TextBox time_text_box) {
_time_text_box = time_text_box;
tick_time = new DelegateCommand<int>(_tick_time);
tick_month = new DelegateCommand<int>(_tick_month);
tick_day = new DelegateCommand<int>(_tick_day);
@@ -135,12 +153,52 @@ namespace mbts.ui.controls {
RaisePropertyChanged(nameof(month_text), nameof(day_text), nameof(year_text), nameof(time_text));
}
}
public bool use_military_time { get; set; }
public ICommand tick_time { get; }
public ICommand tick_month { get; }
public ICommand tick_day { get; }
public ICommand tick_year { get; }
string _time_format => use_military_time ? StringFormats.Time.MilitaryWithSeconds : StringFormats.Time.StandardWithSeconds;
public void tick_time_component(int increment) {
_ensure_time_text_up_to_date();
var r = _result.result;
if (r.HasValue) {
int index = _time_text_box.CaretIndex;
using (detect_property_changes()) {
DateTime dt;
if (index <= 0) {
dt = r.Value.AddHours(increment);
} else {
int segment = _time_text_box.Text.Take(index).Count(c => c == ':');
switch (segment) {
case 0: dt = r.Value.AddHours(increment); break;
case 1: dt = r.Value.AddMinutes(increment); break;
case 2:
if (_time_text_box.Text.Take(index).Contains(' ')) {
// toggle AM/PM
dt = r.Value.AddHours(r.Value.Hour < 12 ? 12 : -12);
} else {
dt = r.Value.AddSeconds(increment);
}
break;
default:
dt = r.Value.AddSeconds(increment);
break;
}
}
_result = TimeTextTuple.FromDateTime(dt, _time_format);
}
_time_text_box.CaretIndex = index;
} else {
using (detect_property_changes())
_force_valid_inputs();
}
}
void _tick_time(int increment) {
_ensure_time_text_up_to_date();
using (detect_property_changes()) {
var r = _result.result;
if (r.HasValue) {
@@ -187,6 +245,11 @@ namespace mbts.ui.controls {
}
}
}
void _ensure_time_text_up_to_date() {
if (_time_text_box.IsKeyboardFocused)
_time_text_box.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
void _force_valid_inputs() {
var dt = DateTime.Today.Date;
@@ -8,6 +8,7 @@ namespace mbts.ui.converters
internal class EnumCharacterCasingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value is null) return string.Empty;
string s = value is Enum ? EnumHelper.get_display_name((Enum)value) : value.ToString();
CharacterCasing casing;
@@ -61,16 +61,16 @@ namespace mbts.ui
_source_window = window as FloatingWindow;
if (_source != null) {
_mouse_start = e.GetPosition(window);
if (!is_button_descendant((DependencyObject)e.OriginalSource)) {
if (!is_clickable_descendant((DependencyObject)e.OriginalSource)) {
Mouse.Capture(_source);
}
}
}
static bool is_button_descendant(DependencyObject target) {
static bool is_clickable_descendant(DependencyObject target) {
while (target != null && !(target is TabDocument)) {
target = VisualTreeHelper.GetParent(target);
if (target is Button) return true;
if (target is Button || target is TextBox) return true;
}
return false;
}
@@ -24,38 +24,7 @@ namespace mbts.ui
}
}
public static async Task FromEvent(this object target, string event_name) {
var evt = target.GetType().GetEvent(event_name);
var delegate_type = evt.EventHandlerType;
var tcs = new TaskCompletionSource<object>();
Action result = () => { tcs.TrySetResult(null); };
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
try {
evt.AddEventHandler(target, d);
await tcs.Task;
}
finally {
evt.RemoveEventHandler(target, d);
}
}
public static async Task<T> FromEvent<T>(this object target, string event_name) {
var evt = target.GetType().GetEvent(event_name);
var delegate_type = evt.EventHandlerType;
var tcs = new TaskCompletionSource<T>();
Action<T> result = (arg) => { tcs.TrySetResult(arg); };
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
try {
evt.AddEventHandler(target, d);
await tcs.Task;
return tcs.Task.Result;
}
finally {
evt.RemoveEventHandler(target, d);
}
}
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this object target, string event_name) {
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this DependencyObject target, string event_name) {
var evt = target.GetType().GetEvent(event_name);
var delegate_type = evt.EventHandlerType;
var tcs = new TaskCompletionSource<Tuple<T1,T2>>();
@@ -70,7 +70,7 @@ namespace mbts.ui
private void _subscribe() {
Disposable.dispose(ref _sub);
_sub = _source.ObserveOnDispatcher().Subscribe(_on_data);
_sub = _source.OnDispatcher().Subscribe(_on_data);
}
private void _on_data(T data) { Target.SetValue(_target_property, _last_value = data); }
@@ -0,0 +1,16 @@
using System.Windows.Input;
namespace mbts.ui {
class CustomInputBinding : InputBinding { }
static class InputGestures {
public static readonly InputGesture LeftTripleClick = new LeftTripleClickGesture();
class LeftTripleClickGesture : InputGesture {
public override bool Matches(object targetElement, InputEventArgs inputEventArgs) =>
inputEventArgs is MouseButtonEventArgs mouse_args &&
mouse_args.ChangedButton == MouseButton.Left &&
mouse_args.ClickCount == 3;
}
}
}
@@ -7,7 +7,14 @@ using System.Windows.Input;
namespace mbts.ui {
static class TextBoxBehavior {
public static readonly RoutedCommand ReleaseCaptureCommand = new RoutedCommand();
#region RoutedCommands/Handlers
public static readonly RoutedCommand
ReleaseCaptureCommand = new RoutedCommand(),
SelectAllCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler SelectAllCommandExecutedHandler =
new ExecutedRoutedEventHandler((s, e) => (e.OriginalSource as TextBox ?? e.Source as TextBox ?? s as TextBox)?.SelectAll());
#endregion
#region SelectAllOnFocus
@@ -168,7 +175,7 @@ namespace mbts.ui {
public static void SetInputFilter(DependencyObject d, TextBoxInputFilter value) => d.SetValue(InputFilterProperty, value);
#endregion
#region
#region IncrementDecrementOnMouseClick
public static readonly DependencyProperty IncrementDecrementOnMouseClickProperty = DependencyProperty.RegisterAttached(
"IncrementDecrementOnMouseClick", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, IncrementDecrementOnMouseClickPropertyChanged)
);
@@ -2,11 +2,28 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui {
static class TextElementHelper {
public static IDisposable SubscribeKeyboardFocus(this TextBox text_box, Action<bool> callback) {
text_box.GotKeyboardFocus += on_got_keyboard_focus;
text_box.LostKeyboardFocus += on_lost_keyboard_focus;
return Disposable.Create(() => {
text_box.GotKeyboardFocus -= on_got_keyboard_focus;
text_box.LostKeyboardFocus -= on_lost_keyboard_focus;
});
void on_got_keyboard_focus(object sender, KeyboardFocusChangedEventArgs e) {
if (e.NewFocus == text_box)
callback(true);
}
void on_lost_keyboard_focus(object sender, KeyboardFocusChangedEventArgs e) {
if (e.OldFocus == text_box)
callback(false);
}
}
namespace mbts.ui
{
public class TextElementHelper
{
public static void GetTargetProperty(DependencyObject d, out FrameworkElement text_element, out DependencyProperty text_property) {
text_element = (FrameworkElement)d;
if (text_element is TextBox) text_property = TextBox.TextProperty;
@@ -22,7 +22,7 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Loaded="style_loaded" x:Name="window_border" u:WindowStyle.IsNonClientArea="true">
<Border BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Loaded="style_loaded" x:Name="window_border" u:WindowStyle.IsNonClientArea="true">
<Grid Margin="{TemplateBinding Margin}">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
@@ -134,8 +134,8 @@
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource self}}" Value="Normal">
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="BorderThickness" Value="7,1,2.66,2.66" TargetName="window_border" />
<Setter Property="Padding" Value="0,0,2,2" />
<Setter Property="BorderThickness" Value="7,1,1,1" TargetName="window_border" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -14,18 +14,19 @@ namespace mbts.ui.shell
public partial class FloatingWindow : Window
{
bool _closed = false;
private IUndoActions _undo;
readonly IBookmarks _bookmark;
private IWindowManager _wm;
private ISavedWindowFactory _window_factory;
private ISettingsCollector _window_settings;
private Stack<Action> _recently_closed_windows = new Stack<Action>();
// if is_loaded is true, will NOT set width/height
internal FloatingWindow(TabDocument document, IWindowManager wm, IUndoActions undo, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
internal FloatingWindow(TabDocument document,
IWindowManager wm, IBookmarks bookmarks, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
_wm = wm;
_undo = undo;
_window_settings = settings;
_window_factory = factory;
_bookmark = bookmarks;
InitializeComponent();
if (string.IsNullOrEmpty(document.ViewModel.host_id)) {
@@ -67,6 +68,7 @@ namespace mbts.ui.shell
private ItemCollection Items { get { return _tabs.Items; } }
public IEnumerable<TabDocument> Documents => _tabs.Items.OfType<TabDocument>();
public bool IsEmpty { get { return _tabs.Items.Count == 0; } }
public bool SingleChild { get { return _tabs.Items.Count == 1; } }
public string host_id { get; private set; }
@@ -88,10 +90,10 @@ namespace mbts.ui.shell
if (sender != this) {
var undoaction = create_undo_action(document);
if (undoaction != null) {
IDisposable undoitem = _undo.push("Restore Tab '{0}'".fmt(document.Title), undoaction);
_recently_closed_windows.Push(() => { undoaction(); undoitem.Dispose(); });
_recently_closed_windows.Push(() => { undoaction(); });
}
}
_bookmark.save_bookmark(document.Model);
}
public void remove_document(TabDocument document) {
@@ -144,22 +146,9 @@ namespace mbts.ui.shell
protected override void OnClosed(EventArgs e) {
_closed = true;
TabDocument[] items = _tabs.Items.OfType<TabDocument>().ToArray();
if (items.Length > 0) {
// If we're closing and still have Tabs open we save the whole "window" worth of state in the undo stack
// If the application is shutting down this might be unnecessary work but it's not too costly
var undo_actions = items.Select(t => create_undo_action(t.ViewModel)).Where(item => item != null).ToArray();
if (undo_actions.Length > 0) {
string name = "Restore Window: '{0}'".fmt(string.Join("', '", items.Select(t => t.ViewModel.Title)));
_undo.push(name, () => {
foreach (var u in undo_actions) { u(); }
});
}
}
base.OnClosed(e);
foreach (TabDocument document in items) {
foreach (TabDocument document in _tabs.Items.OfType<TabDocument>().ToList()) {
if (document != null && document.ViewModel.CloseCommand.CanExecute(this)) {
document.ViewModel.CloseCommand.Execute(this);
}
@@ -29,7 +29,7 @@ namespace mbts.ui.shell
public async Task<IEnumerable<string>> get_workspaces(User user) {
if (!_cache.TryGetValue(user.id, out List<string> result)) {
using (var svc = new UserUISettings(user.id, _persist)) {
using (var svc = new UserUISettings(user.id, _persist, null)) {
var workspaces = await svc.query<WorkspaceDefinition>(WorkspacesModel.WORKSPACE_NAMESPACE, ".*");
result = workspaces.Select(w => w.key).ToList();
_cache[user.id] = result;
+39 -12
View File
@@ -3,7 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Style="{StaticResource {x:Type TabItem}}" d:DesignWidth="125"
xmlns:ui="clr-namespace:mbts.ui"
xmlns:convert="clr-namespace:mbts.ui.converters"
mc:Ignorable="d" d:DesignWidth="125"
Header="{Binding}" Content="{Binding AppContent}"
Background="#e6e6e6">
@@ -18,6 +20,18 @@
<Storyboard x:Key="_slide_back">
<DoubleAnimation Storyboard.TargetName="translate_transform" Storyboard.TargetProperty="X" To="100" Duration="0:0:0.25" FillBehavior="Stop" />
</Storyboard>
<DataTemplate x:Key="_edit_template">
<TextBox Text="{Binding TitleOverride}"
ui:TextBoxBehavior.CommitOnEnter="True"
ui:TextBoxBehavior.FocusOnLoaded="True"
ui:TextBoxBehavior.SelectAllOnFocus="True"
ui:TextBoxBehavior.ReleaseOnEscape="True"
Width="150" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0" />
</DataTemplate>
<DataTemplate x:Key="_template">
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"></TextBlock>
</DataTemplate>
<convert:BooleanToObjectConverter x:Key="MenuHeaderConverter" ValueForTrue="Rename Window" ValueForFalse="Add to My Windows" />
</ResourceDictionary>
</TabItem.Resources>
<TabItem.LayoutTransform>
@@ -28,25 +42,32 @@
<TabItem.RenderTransform>
<TranslateTransform x:Name="translate_transform" X="0" />
</TabItem.RenderTransform>
<TabItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"> </TextBlock>
</DataTemplate>
</TabItem.HeaderTemplate>
<TabItem.Template>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="bd" BorderBrush="Black" BorderThickness="0,0,0.5,0" Background="#d6d6d6" Width="{DynamicResource TabWidth}" Height="{DynamicResource TabHeight}" MinWidth="40" Margin="0" SnapsToDevicePixels="True" DockPanel.Dock="Left">
<Border.ToolTip>
<TextBlock Text="{Binding Title}" />
</Border.ToolTip>
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding HasCustomTitle, Converter={StaticResource MenuHeaderConverter}}" Command="{Binding rename_command}">
<MenuItem.Icon>
<Image Source="{StaticResource Bookmarks}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Remove from My Windows" Command="{Binding unrename_command}" IsEnabled="{Binding HasCustomTitle}" />
</ContextMenu>
</Border.ContextMenu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0" Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0"
Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
<!--<Image MaxHeight="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Stretch="Uniform" Margin="5"
Source="{StaticResource Bookmarks}" Visibility="{Binding HasCustomTitle, Converter={StaticResource BoolToVisibility}}" />-->
<ContentPresenter Grid.Column="1" x:Name="content" ContentSource="Header" TextBlock.Foreground="#666"
RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="Left"
@@ -62,12 +83,18 @@
<Setter Property="Panel.ZIndex" Value="1" />
<Setter Property="Background" TargetName="bd" Value="#F9F9F9" />
<Setter Property="TextBlock.Foreground" TargetName="content" Value="black" />
<!--<Setter Property="BorderThickness" TargetName="bd" Value="0,4,0.5,0" />-->
<!--<Setter Property="Margin" Value="-2,-2,-2,-1" />
<Setter Property="Margin" TargetName="content" Value="0,0,0,1" />
<Setter Property="Margin" TargetName="bd" Value="0,0,0,0" />-->
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</TabItem.Template>
<TabItem.Style>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="HeaderTemplate" Value="{StaticResource _template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding EditMode}" Value="True">
<Setter Property="HeaderTemplate" Value="{StaticResource _edit_template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
</TabItem>
@@ -18,8 +18,15 @@ namespace mbts.ui.shell {
public ImageSource IconSource { get { return _module.icon; } }
public UIElement MenuContent => new SparkMenu(_module.actions, _bus);
public void opening() { }
public void opening() {
(_module as IShellMenu)?.opening();
if (_module is ILazyMenuModule) {
RaisePropertyChanged(nameof(MenuContent));
}
}
public void closing() { }
public void closing() {
(_module as IShellMenu)?.closing();
}
}
}
@@ -24,10 +24,11 @@ namespace mbts.ui.shell
Dispatcher _dispatcher;
HwndContentHost _hwnd_host;
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
Logger.assert(model != null);
_model = model;
_model.TitleChanged += () => RaisePropertyChanged("Title");
_model.TitleChanged += () => { if (!HasCustomTitle) RaisePropertyChanged("Title"); };
_model.IconChanged += () => { _icon = null; RaisePropertyChanged("Icon"); };
_wm = wm;
host_id = host;
@@ -40,13 +41,55 @@ namespace mbts.ui.shell
if (HasTabAffinity) {
this.host_id = ((ITabAffineWindow)model).tab_group;
}
_edit_mode = backing(nameof(EditMode), false);
}
public string host_id { get; set; }
public Dispatcher Dispatcher { get { return _dispatcher; } }
public string Title { get { return _model.Title; } }
public string Title { get { return HasCustomTitle? TitleOverride : _model.Title; } }
string _title_override;
public string TitleOverride {
get => _title_override;
set {
if (_title_override != value) {
if (value == _model.Title) {
_title_override = null;
}
else {
_title_override = value;
}
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
}
_edit_mode.set(false);
}
}
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
Property<bool> _edit_mode;
public bool EditMode => _edit_mode.Value;
public ICommand rename_command => new DelegateCommand(begin_rename);
public ICommand unrename_command => new DelegateCommand(remove_custom);
private void begin_rename() {
if (string.IsNullOrEmpty(TitleOverride)) {
_title_override = Title;
}
_edit_mode.set(true);
}
private void remove_custom() {
TitleOverride = string.Empty;
//begin_close(this);
}
public string OwnerName { get { return _model.Owner == null ? "" : _model.Owner.name; } }
public ICommand CloseCommand { get { return new DelegateCommand(begin_close); } }
public double InitialWidth { get; private set; }
public double InitialHeight { get; private set; }
@@ -155,18 +198,14 @@ namespace mbts.ui.shell
}
}
public byte[] GetSaveState() {
MemoryStream stream = new MemoryStream();
_model.GetState(stream);
return stream.ToArray();
}
public IShellWindow Model { get { return _model; } }
public bool IsSlow { get { return _model is ISlowWindow; } }
public bool IsShared { get { return _model is ISharedWindow; } }
public bool HasTabAffinity { get { return _model is ITabAffineWindow; } }
public void Dispose() {
if (IsShared) {
// Shared threads continue running
+26 -13
View File
@@ -20,25 +20,31 @@ namespace mbts.ui.shell {
[Export(typeof(IWindowManager))]
[Export(typeof(IWindowsInterop))]
[Export]
public class WindowManager : IWindowManager, IWindowsInterop, IDisposable {
Dictionary<IShellWindow, TabDocument> _documents = new Dictionary<IShellWindow, TabDocument>();
Dictionary<string, FloatingWindow> _windows = new Dictionary<string, FloatingWindow>();
IShellThreadFactory _threads;
readonly IShellThreadFactory _threads;
Lazy<ISettingsCollector> _window_settings;
ISavedWindowFactory _window_factory;
IUndoActions _undo;
readonly ISavedWindowFactory _window_factory;
readonly Lazy<IBookmarks> _bookmarks;
IDisposable _dispose;
object _lock = new object();
[ImportingConstructor]
public WindowManager(
IShellThreadFactory threads, Lazy<ISettingsCollector> settings,
ISavedWindowFactory factory, IUndoActions undo, IBus bus) {
ISavedWindowFactory factory, Lazy<IBookmarks> bookmarks, IBus bus) {
_threads = threads;
_window_settings = settings;
_window_factory = factory;
_undo = undo;
_dispose = bus.subscribe(SparkTags.ShowWindow).ObserveOnDispatcher().Subscribe(w => add(w).ignore());
_bookmarks = bookmarks;
_dispose =
Disposable.Create(
bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore()),
bus.subscribe(SparkTags.ShowWindowAsync).OnDispatcher().Subscribe(w => add(w).ignore()),
bus.subscribe(SparkTags.ActivateWindow).OnDispatcher().Subscribe(MakeVisible)
);
}
#region Window creation
@@ -48,6 +54,7 @@ namespace mbts.ui.shell {
var state = model.start_position();
if (state != null) {
viewmodel.host_id = state.host_id ?? "";
viewmodel.TitleOverride = state.custom_title;
}
var window = CreateWindow(content, state);
@@ -66,10 +73,10 @@ namespace mbts.ui.shell {
}
} else {
if (position != null) {
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory, true);
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory, true);
window.set_position(position);
} else
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory);
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory);
_windows.Add(window.host_id, window);
window.Closed += (o, e) => _windows.Remove(window.host_id);
}
@@ -81,12 +88,19 @@ namespace mbts.ui.shell {
public IReadOnlyList<IShellWindow> list_windows() =>
_documents.Keys.ToList();
public IReadOnlyList<TabDocument> list_documents() => _documents.Values.ToList();
public async Task add(IShellWindow window) {
if (!Application.Current.Dispatcher.CheckAccess())
await Application.Current.Dispatcher.InvokeAsync(() => add(window));
Logger.info(bts.log.Subsystem.Ui, $"Opening window {window.diagnostic_name()}.");
lock (_lock)
lock (_lock) {
_documents[window] = _create_document(window);
}
}
public async Task add(Task<IShellWindow> window) {
await add(await window);
}
public void remove(IShellWindow window) {
@@ -139,14 +153,13 @@ namespace mbts.ui.shell {
#region IWindowsInterop
public WindowPosition GetPosition(IShellWindow shell_window) {
return GetPosition(shell_window, null);
}
public WindowPosition GetPosition(IShellWindow shell_window, FloatingWindow owning_window) {
FloatingWindow window = owning_window == null ? window_for(shell_window) : owning_window;
FloatingWindow window = window_for(shell_window);
var doc = window.Documents.FirstOrDefault(td => td.ViewModel.Model == shell_window);
if (window != null) {
WindowPosition pos = window.get_position();
pos.tab_index = window.document_index(shell_window);
pos.host_id = window.host_id;
pos.custom_title = doc?.ViewModel?.TitleOverride;
return pos;
}
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "IShellWindow '{0}' does not have a valid position. It may not be displayed".fmt(shell_window.Title));
@@ -19,6 +19,7 @@ using mbts.persist;
namespace mbts.ui.shell
{
[Export]
public class WorkspacesModel : ViewModelBaseWithValidation
{
@@ -35,10 +36,11 @@ namespace mbts.ui.shell
private bool _closing = false;
private ILauncher _launcher;
private IUndoActions _undo;
readonly IBookmarks _bookmarks;
[ImportingConstructor]
public WorkspacesModel(ISettingsCollector settings, ISavedWindowFactory window_restore,
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist,
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist, IBookmarks bm,
IUserPersist user_persist, ILauncher launcher, IUndoActions undo, [Import("workspace")]string initial_workspace) {
_settings_collector = settings;
_window_restore = window_restore;
@@ -48,6 +50,7 @@ namespace mbts.ui.shell
_modules = modules;
_launcher = launcher;
_undo = undo;
_bookmarks = bm;
_launcher.closing += _launcher_closing;
Workspaces = new ObservableCollection<WorkspaceViewModel>();
init(initial_workspace).ignore();
@@ -150,6 +153,7 @@ namespace mbts.ui.shell
workspace.windows = _settings_collector.get_window_state().ToList();
workspace.modules = _settings_collector.get_module_state().ToList();
workspace.launcher_state = _settings_collector.get_launcher_state();
workspace.named_windows = _bookmarks.bookmarks.ToList();
return workspace;
}
@@ -166,6 +170,7 @@ namespace mbts.ui.shell
await Task.Yield();
}
workspace.launcher_state = _settings_collector.get_launcher_state();
workspace.named_windows = _bookmarks.bookmarks.ToList();
return workspace;
}
@@ -226,10 +231,15 @@ namespace mbts.ui.shell
}
}
// Notify workspace changed
if (WorkspaceLoaded != null) {
WorkspaceLoaded(name);
_bookmarks.clear();
if (def.named_windows != null) {
foreach (var window in def.named_windows) {
_bookmarks.save_bookmark(window);
}
}
// Notify workspace changed
WorkspaceLoaded?.Invoke(name);
}
public Task delete_workspace(string name, WorkspaceDefinition def) {
@@ -244,18 +254,20 @@ namespace mbts.ui.shell
}
return (def.is_shared ? _public : _private).clear(WORKSPACE_NAMESPACE, name);
}
}
// The serialized data for the Workspace
[ProtoContract(Name = "WorkspaceDefinition")]
public class WorkspaceDefinition
{
// Converted the protobuf type ShellSettings to c#
// Avoid re-using id: 1,3,4,5,6,7
[ProtoMember(2, IsRequired = false)]
public List<WindowState> windows { get; set; }
// named windows which are not currently open
[ProtoMember(3)]
public List<WindowState> named_windows { get; set; }
[ProtoMember(9, IsRequired = false)]
public List<ModuleState> modules { get; set; }
@@ -27,7 +27,7 @@ namespace mbts.spark
{
internal class ContractPickerModel : ViewModelBase {
private const string dot = "•";
private const string dot = "•";
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null) {
_bus = bus;
@@ -38,7 +38,7 @@ namespace mbts.spark
_world_view_filter = world_view_filter;
securities_view = CollectionViewSource.GetDefaultView(securities);
(securities_view as ListCollectionView).CustomSort = new SecuritySorter();
_subs.Add(_bus.subscribe(SecuritiesTag).ObserveOnDispatcher().Subscribe(on_securities_changed));
_subs.Add(_bus.subscribe(SecuritiesTag).OnDispatcher().Subscribe(on_securities_changed));
add_criteria_command = new DelegateCommand(
execute: _ => {
@@ -197,10 +197,6 @@ namespace mbts.spark
selected_securities = secs.Select(s => s.Id).ToList();
ok_button_command.TryRaiseCanExecuteChanged();
_compose_criteria_clause_name();
// _security_in_criteria is our fun to be returned to our callers
// test on click -- verify our func works
//var results = all_securities.Select(all => all.Value.security).Where(x => _security_in_criteria(x)).ToList();
}
private void on_securities_changed(IReadOnlyDictionary<uint, SecurityMD> changed_securities) {
@@ -212,10 +208,6 @@ namespace mbts.spark
all_securities = changed_securities;
}
securities.sync_with(all_securities
.Select(entry => entry.Value.security)
);
_update_exchanges();
_update_security_types();
_update_symbols();
@@ -327,7 +319,6 @@ namespace mbts.spark
.Select(s => s.Value.security.Symbol)
.Distinct()
);
}
private void _update_securities() {
Loaded 100 of 316 files, more files were not shown because too many files have changed in this diff. Show more