Compare commits

..
Author SHA1 Message Date
Taha Afzal 843f42c3e3 Merge branch 'trade_analysis' of bitbucket.org:bluetradesys/spark into trade_analysis 2020-04-06 16:00:24 -04:00
Taha Afzal 3afc3d64b6 changed the keys 2020-04-06 16:00:12 -04:00
pfisher 44f2cdd7c0 change aws keys 2020-03-31 11:00:26 -05:00
Taha Afzal 3f36f54781 Added TopBooks to ProcessedBook 2020-03-03 17:09:56 -05:00
Taha Afzal 59e66cf1ef Sniper reads from multiple files recursively 2020-03-02 18:32:19 -05:00
Taha Afzal dec178f133 ignoring all trades that are not making sense 2020-03-02 09:46:10 -05:00
Taha Afzal e9c9a3bad6 Function to get orders for both sides of the trade 2020-03-02 09:46:10 -05:00
Taha Afzal 46a4aa99df Added quote order marketat the time the quote order was updated 2020-03-02 09:46:10 -05:00
Taha Afzal 1097bbcd60 Q leg display 2020-03-02 09:46:10 -05:00
Taha Afzal 840ba7e0b3 Added total qty and no of orders 2020-03-02 09:46:10 -05:00
Taha Afzal 7e7e88cedc Proximity search bug 2020-03-02 09:46:10 -05:00
Taha Afzal 727a3d5b97 Cleaning up some mess 2020-03-02 09:46:10 -05:00
Taha Afzal 3044d5d5ec Bin searching for trades 2020-03-02 09:46:10 -05:00
Taha Afzal 506084242a Refinement of orders/trades 2020-03-02 09:46:10 -05:00
Taha Afzal 067946127a cleaning up 2020-03-02 09:46:10 -05:00
Taha Afzal 297848bcdb Re-org of passive quote detection 2020-03-02 09:46:10 -05:00
Pedro Pinto 376d64cfbe updated db script to incorporate timestamps 2020-02-06 10:07:14 -05:00
Pedro Pinto bdf3ae7c92 Added unix timestamps to csvs 2020-02-06 09:22:22 -05:00
Pedro Pinto 97d0637d88 Updated tools 2020-02-04 09:27:19 -05:00
Pedro Pinto b4d85cdadf Merge branch 'trade_analysis' of bitbucket.org:bluetradesys/spark into trade_analysis 2020-02-04 09:24:40 -05:00
Pedro Pinto 2b0cb84069 Changed synchronization script to default to synching yesterday 2020-02-04 09:23:25 -05:00
Pedro Pinto 0412c45ed3 Removed build step 2020-02-03 15:48:20 -05:00
Pedro Pinto b984613b8a Non interactive use of postgres 2020-02-03 15:23:21 -05:00
Pedro Pinto e712433d6f More tweaks. 2020-02-03 15:21:27 -05:00
Pedro Pinto 4d8b20d084 Fixed path in postgres sql. 2020-02-03 15:14:08 -05:00
Pedro Pinto df86187457 Tool tweaks 2020-02-03 14:56:01 -05:00
Pedro Pinto 620ea4b4af Tweaked date tool 2020-02-03 13:56:21 -05:00
Pedro Pinto e2951b4436 Added pgadmin tool 2020-02-03 10:12:32 -05:00
Pedro Pinto 0ebc3cf111 Fixed a few issue with analysis tools 2020-02-03 09:58:39 -05:00
Pedro Pinto fd9b2990ae Added easier to use tools for analysis 2020-01-31 16:28:18 -05:00
Pedro Pinto 15d24f96c2 Updated externals, private tools 2020-01-31 16:28:18 -05:00
Pedro Pinto b1bbc6ab0d Updated docker 2020-01-31 16:28:17 -05:00
Pedro Pinto 39be9e41c8 Rewound externals because tip is broken 2020-01-31 16:28:17 -05:00
Pedro Pinto fd91b78742 sync/parse/load tools 2020-01-31 16:28:17 -05:00
Pedro Pinto 95a5dd0ceb updated docker 2020-01-31 16:28:17 -05:00
Pedro Pinto a9aa76deed Updated gitignore and externals 2020-01-31 16:28:17 -05:00
Taha Afzal a80a4f327e Fixed theoutput data zero fills 2020-01-31 13:51:52 -05:00
Taha Afzal 7e9a853a50 filtering books 2020-01-31 11:38:11 -05:00
Taha Afzal 0e0d4412b8 Merge branch 'trade_analysis' of bitbucket.org:bluetradesys/spark into trade_analysis 2020-01-30 22:45:06 -05:00
Taha Afzal b3a19eab05 Filtering quote fills for orders 2020-01-30 22:44:42 -05:00
Pedro Pinto fdb1826e01 Added a tool to download cme historical data 2020-01-30 13:27:02 -05:00
Taha Afzal e2d6bc1c45 Order parsing 2020-01-29 19:43:36 -05:00
Taha Afzal d5a06cc3e7 circle filling in 2020-01-27 22:58:22 -05:00
Taha Afzal 3e7c2e3c18 circle analysis 2020-01-22 11:50:49 -05:00
Taha Afzal ea6eaf6e1d contracts show in entry:exit 2020-01-21 10:40:11 -05:00
Taha Afzal dea2358c51 cleaned up some wrappers to simplify the code 2020-01-17 18:43:08 -05:00
Taha Afzal 491d0c0b7a no exponent mapping 2020-01-17 11:50:06 -05:00
Taha Afzal f593b457a5 Market close times factored in 2020-01-17 11:25:26 -05:00
Taha Afzal 10a3ba52f4 Added max processing time 2020-01-16 11:03:27 -05:00
Taha Afzal 56bc7f216a Parallelized the sniper analysis across file (in batches) 2020-01-15 22:11:04 -05:00
Taha Afzal a57b99b2e8 BEtter formatting of output 2020-01-14 14:49:40 -05:00
Taha Afzal d3a0595934 Cleaning 2020-01-14 10:54:55 -05:00
Taha Afzal a659fbc7cd Sniper analysis works on multiple files now 2020-01-14 09:32:08 -05:00
Taha Afzal 542b8e946d mass sniper analysis 2020-01-13 11:16:06 -05:00
Taha Afzal d4d1f8f533 download args changed 2020-01-12 18:18:56 -05:00
Taha Afzal 624a432c44 ignoring ticks from when the market is closed 2020-01-12 13:13:05 -05:00
Pedro Pinto 68f09c2e27 Fixed crash when the timen take to parse was rounded to 0 secs. 2020-01-11 18:45:36 -05:00
Taha Afzal 3d22a58bac ParseHistoricalFIX takes a path and processes all files with name _raw in it 2020-01-10 19:19:52 -05:00
Taha Afzal 33e46b2fb1 Batch download 2020-01-10 11:14:25 -05:00
Taha Afzal cb357440ed cleaned up some old code 2020-01-09 14:12:04 -05:00
Taha Afzal a88670b95d Added headers to the output 2020-01-09 13:22:39 -05:00
Taha Afzal bb70db70b5 Better output generation 2020-01-09 11:28:41 -05:00
Taha Afzal 2c3e4b7d4d loading and validating 2020-01-06 20:04:45 -05:00
Taha Afzal 843c4f447f Merge branch 'trade_analysis' of bitbucket.org:bluetradesys/spark into trade_analysis 2020-01-03 17:07:26 -05:00
Taha Afzal d41c76e6a3 validatio 2020-01-03 17:06:55 -05:00
Pedro Pinto 805722cdc7 Fixed the calculation of the book in the historical data parser. 2020-01-03 16:07:32 -05:00
Pedro Pinto ef64d3c25e Fixed weird auto&& in HandleIndex.h 2020-01-03 12:27:52 -05:00
Pedro Pinto 0b0cf744a6 Added more information to parser 2020-01-03 12:22:44 -05:00
Pedro Pinto c3410db0aa Fixed clang build. 2020-01-03 12:22:44 -05:00
Eric Harding c5a89b0bb6 fix a couple clang warnings 2020-01-03 11:45:44 -05:00
Taha Afzal 89f5f94588 validation 2020-01-02 19:39:50 -05:00
Taha Afzal c8ac56b0a1 Merge branch 'trade_analysis' of bitbucket.org:bluetradesys/spark into trade_analysis 2020-01-02 19:33:15 -05:00
Pedro Pinto b152b2b853 Added a parser of historical MD to facilitate analysis. 2020-01-02 14:01:57 -05:00
Pedro Pinto 354390ed11 Fixed clang build. 2020-01-02 14:01:48 -05:00
Taha Afzal 08839aadb2 book validation code 2020-01-01 22:03:23 -05:00
Taha Afzal b7cb17500c Downloading new book data 2019-12-31 18:32:45 -05:00
Taha Afzal 01b50e1138 Added function skeleton to compure possible fills 2019-12-30 19:33:04 -05:00
Taha Afzal 8fdb7485ba new order and trade protos 2019-12-30 19:22:40 -05:00
Taha Afzal 293d51038f Circle spreads initial analysis 2019-12-27 18:55:54 -05:00
Taha Afzal 8db81fa248 countering for the trades and orders files 2019-12-27 10:27:18 -05:00
Taha Afzal 47628089ac Recording trades and orders 2019-12-26 21:08:49 -05:00
Taha Afzal 8b2c5a60aa Added NG exp 2019-12-24 12:41:35 -05:00
Taha Afzal f1df673e61 spread parsing gix, Z29 showed up 2019-12-23 18:43:14 -05:00
Taha Afzal 9faee77360 merging vwap 2019-12-23 16:38:58 -05:00
Kevin Darby 0fd45ba92a . 2019-12-23 00:15:10 -05:00
Kevin Darby 4e440fc218 debugging KFillModel 2019-12-22 16:46:06 -05:00
Kevin Darby 41c8351709 ready to test KFillModel 2019-12-22 15:14:38 -05:00
Kevin Darby 1238648c2b leveling applies to those orders that did not receive prorata 2019-12-20 23:43:34 -05:00
Kevin Darby 03ac88683f KFillModel looks to be code complete, needs debug / test 2019-12-20 23:37:39 -05:00
Kevin Darby fe4c057b20 working on KFillModel 2019-12-20 18:18:40 -05:00
Kevin Darby e6c6997e46 prorata start 2019-12-19 23:47:41 -05:00
Kevin Darby ddd8778bab added some comments for taha, some display stuff for notebook 2019-12-19 18:09:02 -05:00
Kevin Darby f95d258210 put real life data with notebooks 2019-12-19 17:13:00 -05:00
Kevin Darby a2e1cf8dd5 moving code around 2019-12-19 15:54:37 -05:00
Kevin Darby 0b8b68e6e6 Merge branch 'csv_export' of bitbucket.org:bluetradesys/spark into vwap 2019-12-19 14:48:38 -05:00
Kevin Darby 8658a11d1f 100 ticks in one day...not enough 2019-12-19 11:20:58 -05:00
Kevin Darby d0aa90131a top doesn't do much 2019-12-19 00:35:11 -05:00
Kevin Darby ffb30177b1 trying top 2019-12-19 00:24:57 -05:00
Kevin Darby 297093f9ce honing in 2019-12-18 23:41:31 -05:00
Kevin Darby 5ba8440877 couple small bugs and some diagnostics 2019-12-18 22:35:35 -05:00
Kevin Darby 228e49d152 taking this home 2019-12-18 18:28:39 -05:00
Kevin Darby a9e983a717 we rollin 2019-12-18 15:32:19 -05:00
Jake Norbut 25445d58d0 refactoring TaS CSV export 2019-12-18 13:48:14 -06:00
Jake Norbut 0b313a8aa8 refactoring Fills CSV export 2019-12-18 13:47:36 -06:00
Jake Norbut 54f954fd4b adding CSV export to order book 2019-12-18 13:47:03 -06:00
Jake Norbut 980f38ba03 using OrderedDictionary to CSVs are output with a predictable column order 2019-12-18 13:46:22 -06:00
Kevin Darby 198086e639 got roll going 2019-12-18 13:16:49 -05:00
Jake Norbut 9eada2724b Having the Command be on the Window rather than the VM; nice thinking John 2019-12-18 10:11:10 -06:00
Jake Norbut b5f53ee918 adding csv_properties to TimeAndSalesVM.Row 2019-12-18 10:10:04 -06:00
Kevin Darby a3a8eb14ad trying another way 2019-12-18 00:07:34 -05:00
Kevin Darby 333acf0822 progress 2019-12-17 18:17:45 -05:00
Jake Norbut efc175f654 adding export to csv command to Fills window 2019-12-17 10:32:58 -06:00
Jake Norbut 6074afd999 adding csv export command to fills vm 2019-12-17 10:32:15 -06:00
Jake Norbut 219dff4055 adding new export_csv IDialogCommand which just creates a new file 2019-12-17 10:28:57 -06:00
Jake Norbut 9a3b07b622 adding ToCsv IEnumerable ext method in a new util namespace 2019-12-17 10:21:38 -06:00
Kevin Darby 1af49376b6 progress in passive fills model 2019-12-16 23:42:58 -05:00
Kevin Darby f90f6cfb33 using sortedlist for our orders in fifo 2019-12-16 23:27:29 -05:00
Kevin Darby 6433f9ba98 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-12-16 21:39:53 -05:00
Kevin Darby cc1882ea1a trying to add iceberg reup to passive fill stream 2019-12-16 18:46:30 -05:00
Kevin Darby 56e83a1e69 fix timestamp bug 2019-12-16 15:17:59 -05:00
Eric Harding 2688281a10 Protect against ICE 0-leg spread data on cert 2019-12-16 10:26:34 -05:00
Kevin Darby 69d429fa4e nb 2019-12-15 23:08:36 -05:00
Kevin Darby e8c9e70a4a change name of methods 2019-12-15 21:30:22 -05:00
Kevin Darby 6c78850902 need to deduct from our orders in fifo model 2019-12-15 14:24:14 -05:00
Kevin Darby adab377e44 got some passive fills, need to verify 2019-12-13 23:09:31 -05:00
Kevin Darby 7bdaa35768 passive fifo fill model 2019-12-13 22:14:02 -05:00
Kevin Darby 1c7f79f293 icomparer 2019-12-13 18:18:47 -05:00
Kevin Darby 3a7246c5b0 time to leave 2019-12-13 18:16:59 -05:00
Taha Afzal 17a1190e34 added exponents for HE,ZC 2019-12-13 18:16:13 -05:00
Kevin Darby 3bd95de839 bugs 2019-12-13 18:14:23 -05:00
Kevin Darby 576d3836b3 fills by query 2019-12-13 18:05:41 -05:00
Kevin Darby f1788b172c trying to layer these as queries 2019-12-13 16:53:56 -05:00
Taha Afzal 82834dc9e6 Parallelized the processing of file for sniper 2019-12-13 14:46:30 -05:00
Kevin Darby ae5912d41d now we're cookin 2019-12-13 14:36:15 -05:00
Kevin Darby adbab4057f renames 2019-12-13 14:15:29 -05:00
Kevin Darby ca4a639b86 changed IFillModel to IMatchingModel, because that's really what it is, now I need fill model 2019-12-13 14:12:55 -05:00
Kevin Darby a539a06f45 interim push to go more stateless 2019-12-13 13:33:18 -05:00
Taha Afzal 2ff96e3528 more helper functions 2019-12-13 12:37:17 -05:00
Kevin Darby 1a87544ccb orders 2019-12-12 23:51:18 -05:00
Kevin Darby b97450297e update notes 2019-12-12 23:10:07 -05:00
Kevin Darby 6a1040a339 regen for trade counter parties 2019-12-12 23:07:49 -05:00
Kevin Darby 7fe20129d2 seems like we really can't count on the order of trade vs delete, so we have to use the price book to approximate crosses 2019-12-12 22:46:12 -05:00
Kevin Darby 4f59c1bb47 1 based arrays, so good 2019-12-12 18:26:10 -05:00
Kevin Darby 3ba17f1c87 allow disk in query 2019-12-12 18:19:08 -05:00
Kevin Darby ef874dda7e bigblue is slow, but I think we're on the right track now 2019-12-12 18:10:03 -05:00
Kevin Darby 6e966a6229 write the data with orders in same mongo document 2019-12-12 16:56:43 -05:00
Kevin Darby ed3b890c67 might have to store the data a little differently 2019-12-12 15:16:02 -05:00
Kevin Darby 2a46fd6227 push for pete 2019-12-12 14:27:28 -05:00
Taha Afzal 74f1bf2480 functions to remove duplicates 2019-12-12 11:35:19 -05:00
Kevin Darby 23e13846c6 botched order priority when only reducing size 2019-12-12 10:13:40 -05:00
Kevin Darby 39e4549972 only got one fill 2019-12-11 22:55:12 -05:00
Kevin Darby d924962546 kinda starting to work 2019-12-11 18:11:13 -05:00
Kevin Darby 0f1c81eb48 roundtrip 2019-12-11 18:07:33 -05:00
Kevin Darby 5e6fe7282d moved code around verified roll_snipe still works 2019-12-11 17:02:47 -05:00
Taha Afzal 4e48bcf6b5 refactoring 2019-12-11 13:33:07 -05:00
Kevin Darby a47dad1ce4 changes in algo backtester to facilitate jupyter, will probably remove backwards compatibility in a bit 2019-12-11 13:28:48 -05:00
Taha Afzal 6fd829df49 Silly bug that was overwriting the lined up markets 2019-12-11 11:49:30 -05:00
Kevin Darby 1809c778e2 ialgo 2019-12-10 23:40:55 -05:00
Kevin Darby 2178e16d3c restructure for jupyter 2019-12-10 22:50:49 -05:00
Kevin Darby deadbc6cf2 we actually do want one row per order for better query perf 2019-12-10 21:14:57 -05:00
Kevin Darby 445d77223f move some stuff around 2019-12-10 18:18:05 -05:00
Kevin Darby 09b24e33fc Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-12-09 14:05:56 -06:00
Kevin Darby 84553d11c4 new_externals 2019-12-09 14:05:04 -06:00
Kevin Darby eb1bab5878 linux build 2019-12-09 14:18:05 -05:00
Kevin Darby a7a58038b3 release build 2019-12-09 14:15:04 -05:00
Kevin Darby 1b9af8f010 new externals 2019-12-09 11:45:10 -05:00
Kevin Darby 113c4fe539 changes in notebooks, I am matching Taha 2019-12-09 11:12:06 -05:00
Kevin Darby c33662dc2c py 2019-12-09 00:30:36 -05:00
Taha Afzal 0dcb3d3dfd Added microscope for a closer look! 2019-12-08 21:53:39 -05:00
Kevin Darby 8817b22376 write book with trades 2019-12-08 14:37:48 -05:00
Taha Afzal 212f2a3d36 can run the sniper on one synthetic only, too 2019-12-08 10:39:04 -05:00
Kevin Darby b48da514e5 I think we got it 2019-12-08 00:18:25 -05:00
Taha Afzal 649225ef6c unifying the spread calc function 2019-12-07 22:55:18 -05:00
Kevin Darby 59cba2456b honing in on some timing issue 2019-12-07 22:02:45 -05:00
Kevin Darby 839c3d430b fixed build 2019-12-07 16:27:30 -05:00
Taha Afzal 35495dc7cf better formatting 2019-12-06 20:17:14 -05:00
Kevin Darby 5b1f2522e1 found bug in synth processor, won't build until I fix 2019-12-06 18:29:58 -05:00
Taha Afzal 8ee78c83d2 writes result to a file: 2019-12-06 18:19:29 -05:00
Kevin Darby 6c3ccdf856 that's 400 micros, not nanos 2019-12-06 14:30:15 -05:00
Kevin Darby 157d834cbb getting somewhere with the roll 2019-12-06 13:42:40 -05:00
Taha Afzal b4beb7c0db moved BookEntry and TimeRange into protos 2019-12-06 13:19:03 -05:00
Kevin Darby 4f2e9b19cc trying to merge some collections 2019-12-05 23:49:11 -05:00
Kevin Darby fed7ca1ff3 honing in 2019-12-05 18:20:28 -05:00
Taha Afzal f84517c15c cleaning up some mes 2019-12-05 17:32:05 -05:00
Taha Afzal 075354a975 Sniper analysis works on generated spreaders 2019-12-05 11:08:45 -05:00
Kevin Darby 73cfa17791 notes 2019-12-05 10:50:54 -05:00
Kevin Darby 474a53ba92 change notebook destination to big blue 2019-12-05 10:50:10 -05:00
Kevin Darby 69612eaee7 thought better of two tables, this makes simulation way easier 2019-12-04 23:33:48 -05:00
Kevin Darby b7ad777de4 have to go back to orders stream 2019-12-04 22:44:22 -05:00
Kevin Darby d6a70d852e added orders to the mongo 2019-12-04 18:10:47 -05:00
Taha Afzal 4450476a40 simplified lining up of books 2019-12-04 09:49:26 -05:00
Kevin Darby 33f12edce5 finally figured out timestamps 2019-12-03 23:47:48 -05:00
Kevin Darby ab2e9fe6bd I might have fixed the dates 2019-12-03 22:34:04 -05:00
Kevin Darby 8bcffea686 jupyter 2019-12-03 18:17:31 -05:00
Kevin Darby 941472c5c0 bug in spread processing, small things in simulation 2019-12-02 23:35:42 -05:00
Kevin Darby d2a5e03976 notes 2019-12-02 18:14:54 -05:00
Kevin Darby 97e3bce327 moving historical analysis stuff to spark_historical 2019-12-02 16:06:05 -05:00
Kevin Darby e766667584 <= vs < 2019-12-02 14:48:54 -05:00
Kevin Darby a93c110c29 better serialization for pbuf Timestamps 2019-12-02 14:43:29 -05:00
Taha Afzal 3b0bbe6908 simplified 2019-12-02 13:09:11 -05:00
Taha Afzal 1147edcbb9 tests 2019-12-02 12:44:10 -05:00
Kevin Darby e1c7b89408 mongo 2019-12-01 21:38:55 -05:00
Taha Afzal c50133ba02 tests 2019-12-01 16:17:14 -05:00
Taha Afzal f1dbacb989 forcing an exponent to fix the prices 2019-11-29 16:43:21 -05:00
Kevin Darby ad3ce72a39 added a mongo generator, much nicer 2019-11-29 16:35:46 -05:00
Kevin Darby 57f48472d3 trying out timescaledb, 2019-11-29 15:47:20 -05:00
Taha Afzal 0535eedbbf merging 2019-11-29 15:34:44 -05:00
Kevin Darby 45ae6ac585 parsing this data needs to get faster 2019-11-29 13:33:06 -05:00
Taha Afzal 2740de913c BookState is now called BookSeries 2019-11-29 09:47:46 -05:00
Kevin Darby 5a90676a25 need a finally 2019-11-27 21:57:03 -05:00
Kevin Darby 8536f782ce Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 21:47:03 -05:00
Kevin Darby 5f45ddf210 wack vs backwack 2019-11-28 02:46:27 +00:00
Kevin Darby 67c83c7d68 remove postgres tag from spark_vm_shell 2019-11-27 21:10:22 -05:00
Kevin Darby 6f6e1a9e55 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 21:01:43 -05:00
Kevin Darby c80451bc5e added async 2019-11-27 18:20:14 -05:00
Taha Afzal ba53543e43 Parsing the sniper input file correctly 2019-11-27 18:07:59 -05:00
Kevin Darby d6a533ca5c PR feedback 2019-11-27 17:21:24 -05:00
Kevin Darby e0112eff89 stuff 2019-11-27 17:20:55 -05:00
Kevin Darby 38b29f0afb more pg 2019-11-27 17:14:29 -05:00
Kevin Darby 8b9d689a5f able to preprocess trades and books into a postgres 2019-11-27 16:18:56 -05:00
Kevin Darby 097edb2726 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 15:09:58 -05:00
Kevin Darby 12e3e9a222 gave up and decided to run postgres in docker on my host 2019-11-27 15:08:51 -05:00
Taha Afzal ce19ba1419 added MarketQueriesHelper 2019-11-27 14:04:32 -05:00
Taha Afzal c81a22e55d Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 11:50:45 -05:00
Taha Afzal bd04f49ff5 Reorg 2019-11-27 11:50:38 -05:00
Eric Harding 3a9e8970d6 merge temp_synthetic_compute 2019-11-27 10:28:22 -05:00
Taha Afzal 9837645d16 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 09:34:43 -05:00
Taha Afzal 1789c02295 skeleton in place 2019-11-27 09:34:28 -05:00
Kevin Darby 3280376df3 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-26 21:31:42 -05:00
Taha Afzal d251593f04 parsing sniper args 2019-11-26 19:49:51 -05:00
Kevin Darby 9afeb29c63 pg 2019-11-26 18:21:06 -05:00
Kevin Darby e4dc07ce3f Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-26 17:40:01 -05:00
Kevin Darby 2bbf22a153 new docker for postges some db code 2019-11-26 16:03:43 -05:00
Taha Afzal a598ae8bd2 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-26 15:40:19 -05:00
Taha Afzal 4d72b9d2e7 filling in 2019-11-26 15:40:10 -05:00
Kevin Darby e000b1d0c0 hmm 2019-11-26 00:07:36 -05:00
Kevin Darby ef4261fe7f small parser tweaks for taha / me 2019-11-25 23:17:30 -05:00
Kevin Darby c3b75aa742 rename packet variable 2019-11-25 15:13:30 -05:00
Taha Afzal 2518be52fb book post processing path formalized 2019-11-25 15:12:29 -05:00
Kevin Darby f3ee6c0e30 TWAP completes 2019-11-25 14:54:15 -05:00
Kevin Darby 94363767ab small bug in slicer 2019-11-25 14:10:54 -05:00
Taha Afzal 9dd6e39665 post processing of books + helper functions to allign boks 2019-11-25 09:34:38 -05:00
Eric Harding b62d9751cf chown duplicates files in docker image 2019-11-20 14:57:56 -05:00
Jake Norbut 26b5fe5699 updating release notes 2019-11-20 09:47:48 -06:00
Jake Norbut 2fccceb91d Merged in ladder_recenter_no_kb (pull request #379)
Ladder recenter no kb
2019-11-20 15:47:09 +00:00
Jake Norbut fa49a0de07 adding new test 2019-11-20 09:45:51 -06:00
Jake Norbut bd28a0fa54 delete old test 2019-11-20 09:45:27 -06:00
Jake Norbut 14ebe5ef51 (shift or alt) + double click to recenter all ladders 2019-11-20 09:14:20 -06:00
Jake Norbut a9b4049307 relocating bus.post of recenter all to common public ladder function 2019-11-20 09:13:58 -06:00
Jake Norbut 234d499731 adding ladder spacebar test 2019-11-19 08:51:38 -06:00
Eric Harding 324b9c1cc7 Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2019-11-19 09:04:33 -05:00
Kevin Darby 722ae68cf1 more 2019-11-18 15:08:09 -05:00
John Colvin dbe2194ed3 Merged in SP-265-pnl-cast-exception (pull request #376)
SP-265 pnl cast exception

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-11-18 15:26:06 +00:00
Eric Harding e39313ecf6 Add merge_synthetic_book sample test code 2019-11-15 17:42:49 -05:00
Eric Harding 8095100bee p/invoke for synthetic book computation 2019-11-15 16:11:02 -05:00
John Colvin 45e10ca377 SP-265 use 'is' instead of explicit cast to IPNLCellData (the cast should work; this is defensive) 2019-11-14 21:09:31 -06:00
John Colvin 4de77ce339 pnl sometimes duplicates grouping columns when they are changed in settings dialog 2019-11-14 16:12:58 -06:00
John Colvin 96f71290a4 SP-265 pnl crash due to CastException 2019-11-14 16:12:19 -06:00
Kevin Darby a76415a52f native algo state 2019-11-14 17:04:42 -05:00
Kevin Darby 06305ee4ac having trouble routing fills down to c++ 2019-11-14 16:21:51 -05:00
Kevin Darby 70e87151bc upstream changes for PR 2019-11-13 16:49:17 -05:00
Kevin Darby d1299f8a78 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-13 16:47:18 -05:00
Kevin Darby 20e58dc80b address PR concerns 2019-11-13 16:46:36 -05:00
Kevin Darby 9f7b0e5898 running twap from c#, need some stats 2019-11-12 18:12:06 -05:00
Kevin Darby 5e073c2330 interop with c++ algos kinda works 2019-11-12 17:56:25 -05:00
Kevin Darby 1b425d9a7c Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-12 11:31:31 -05:00
Kevin Darby 3546b65c91 don't finalize 2019-11-12 11:18:24 -05:00
Kevin Darby 758da29e25 added assert I think works 2019-11-11 23:19:03 -05:00
Kevin Darby 60e246036b back to state where I can repl dan 2019-11-11 23:14:49 -05:00
Kevin Darby 4b34f959e6 skip default 2019-11-11 18:17:29 -05:00
Kevin Darby 8abbd24cbe bug in orderbook refactor 2019-11-11 17:57:19 -05:00
Kevin Darby 932f852d6a Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-11 17:14:16 -05:00
Kevin Darby 5773945791 add data dir for .net in linux
make sparkcmd build in the right place on windows again
2019-11-11 17:12:55 -05:00
Kevin Darby bda1bd8f96 round trip algo runs on linux 2019-11-11 17:11:27 -05:00
Kevin Darby c3632e02d3 Added NativeOrderWrapper for algo backtesting 2019-11-08 18:04:34 -05:00
Kevin Darby 1b959b09c8 hooking c++ algos to algo backtest framework 2019-11-07 18:01:21 -05:00
Kevin Darby f55637ea08 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-06 21:36:57 -05:00
Kevin Darby 0272df9974 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-06 21:34:45 -05:00
Kevin Darby 0e02428b79 change algoanalyzer startup for taha 2019-11-06 21:34:35 -05:00
Kevin Darby 721c889235 release mode uses published sparkcmd 2019-11-06 18:22:06 -05:00
Kevin Darby 9957af655f hooked sparkcmd linux build to the main server build stuff 2019-11-06 17:47:21 -05:00
Kevin Darby eab92fb486 damn windows casing 2019-11-06 13:55:29 -05:00
Kevin Darby 9f1cd47cfa hooking build to cmake 2019-11-06 13:55:07 -05:00
Kevin Darby 90c3edbbe7 adding core 2 back to docker per eric 2019-11-06 13:24:57 -05:00
Kevin Darby adee8b584c capitalized first letter of proto to work around a protoc bug 2019-11-06 12:43:27 -05:00
Kevin Darby 329ad323e0 delete protos to change case on windows 2019-11-06 12:41:37 -05:00
Kevin Darby a8473f9340 Capitalizing first letter of some protos because of a protoc bug 2019-11-06 12:31:58 -05:00
Kevin Darby 1c0115f8cf had to delete these protos to change case 2019-11-06 12:31:12 -05:00
Kevin Darby a6ef802d3f fix proj files 2019-11-05 18:38:17 -05:00
Kevin Darby 37b5c37015 merging dotnet branch 2019-11-05 18:24:24 -05:00
Kevin Darby 1e28e214a4 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-05 18:23:47 -05:00
Kevin Darby c713b97441 cleanup csproj 2019-11-05 18:23:09 -05:00
Kevin Darby 4e8525f015 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-05 18:21:40 -05:00
Kevin Darby eb79264b9b netcore builds on linux through dotnet build 2019-11-05 18:19:17 -05:00
Kevin Darby 9c359ddb33 for_taha 2019-11-05 16:38:01 -05:00
Kevin Darby 67a16d5085 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-11-03 23:20:44 -05:00
Kevin Darby 6049cc4b43 oil spreads seem to work 2019-10-26 22:38:13 -04:00
Kevin Darby d916180132 back to oil 2019-10-25 23:50:20 -04:00
Kevin Darby ad9f28f782 round trip treasuries...not great 2019-10-25 23:32:02 -04:00
Kevin Darby 2890e4ebcc rt spreader 2019-10-25 18:56:52 -04:00
Kevin Darby 6638e4c765 fixed spreads / outright arbitration 2019-10-25 18:50:04 -04:00
Kevin Darby a251a8f411 clone 2019-10-25 00:50:02 -04:00
Kevin Darby e7790679ca this isn't as fast as I thought it would be 2019-10-25 00:15:34 -04:00
Kevin Darby 2a5e777c15 this is better 2019-10-24 18:30:24 -04:00
Kevin Darby fdbbce9ff3 remove concept of pre_book, instead separate the book arrangement and the trade / cross processing and layer in our orders by timestamp 2019-10-24 18:25:06 -04:00
Kevin Darby d47ac768c4 trying something 2019-10-24 16:30:21 -04:00
Kevin Darby 4867f4fa53 match event 2019-10-24 15:53:16 -04:00
Kevin Darby 902a99e7d0 new files 2019-10-24 14:30:13 -04:00
Kevin Darby faf5f664dc share one order book 2019-10-24 14:29:35 -04:00
Kevin Darby 4af94aaaa8 made some trades 2019-10-23 23:52:23 -04:00
Kevin Darby 2bea0a0a11 replicated some CL spread arbs 2019-10-23 18:34:40 -04:00
Kevin Darby b55067be91 trying 10/21 oil spreads 2019-10-23 00:24:21 -04:00
Kevin Darby 7ea20dbbff had to introduce concept of can_aggress-- if we're first in the queue we can't tell the difference between that and a cross 2019-10-22 23:50:07 -04:00
Kevin Darby 2dcf1424ea . 2019-10-22 18:21:30 -04:00
Kevin Darby caedb1ed64 round trip spreader taking shape 2019-10-22 18:15:13 -04:00
Kevin Darby d39856a93a refactor for multiple securities, implemented round tripper 2019-10-22 16:27:01 -04:00
Kevin Darby f8c7cc87b9 complicated hierarchies 2019-10-22 00:07:35 -04:00
Kevin Darby ee9a9bd721 moving stuff around for spreader case 2019-10-21 18:21:21 -04:00
Kevin Darby 7520d3516a move pnl computation to orderbook 2019-10-21 12:37:12 -04:00
Kevin Darby a2a9df86fc now we need prorata 2019-10-20 22:18:36 -04:00
Kevin Darby aa31aa7a3e testing 2019-10-20 21:27:19 -04:00
Kevin Darby 950b8686f2 small bug here in change / cancel 2019-10-20 21:22:50 -04:00
Kevin Darby b6ab217a11 assert that we are moving forward in time 2019-10-20 21:21:59 -04:00
Kevin Darby 3b8176d326 status event 2019-10-20 21:19:49 -04:00
Kevin Darby e9ec07cb2b have to queue the hedge with latency 2019-10-20 01:13:12 -04:00
Kevin Darby 78659b5d20 if these treasury results are real we are getting into a new business 2019-10-20 00:25:07 -04:00
Kevin Darby 9e26a2c651 adding top 2019-10-19 21:42:08 -04:00
Kevin Darby 3243ce1a45 treasury roll 2019-10-18 23:58:11 -04:00
Kevin Darby 475b6ae25b peg, cover, lean 2019-10-18 22:48:17 -04:00
Kevin Darby b853af59d6 usings 2019-10-18 22:24:10 -04:00
Kevin Darby ded1ad42e7 arbitraged spread and futures streams, separated replay from sending 2019-10-18 22:13:30 -04:00
Kevin Darby f3541b6c7f added latency to the catalyst loop; my clock isn't great though because it relies on the tape 2019-10-18 17:21:55 -04:00
Kevin Darby 3c40c84c02 got the mbo fixed 2019-10-18 16:47:02 -04:00
Kevin Darby d50420edb9 cme can send order updates in MDEntry or OrderIdEntry.... 2019-10-18 15:06:37 -04:00
Kevin Darby e0794f0725 added some assertions, cleaning up, now have to look at q 2019-10-18 13:23:40 -04:00
Kevin Darby fb6282ae9e optimizations 2019-10-18 11:15:08 -04:00
Kevin Darby 23995e5cc6 shaping up 2019-10-17 23:52:20 -04:00
Kevin Darby 161545b864 jcl is small loss always joining at end of q 2019-10-17 22:10:30 -04:00
Kevin Darby 285250dded bugs 2019-10-17 18:22:23 -04:00
Kevin Darby bbc3e03e48 now lets look at this queue 2019-10-17 18:04:03 -04:00
Kevin Darby 6b8d0d203c partials work, the queue seems to have broken 2019-10-17 17:42:03 -04:00
Kevin Darby c0bec8cfa5 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-17 12:18:01 -04:00
Kevin Darby 19b0d8bf56 typo that I need to fix in master 2019-10-17 11:47:46 -04:00
Kevin Darby ede14ea7b3 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-17 11:40:24 -04:00
Kevin Darby 48ae7c4c33 this looks a lot like our c++ interface; I do not know if that's good 2019-10-17 11:36:35 -04:00
Kevin Darby fc217fd904 sweet replication 2019-10-16 23:23:16 -04:00
Kevin Darby b692a3e9d2 partial fills kinda kill everything 2019-10-16 18:37:13 -04:00
Kevin Darby 48f5e47335 now partial fills 2019-10-16 15:38:43 -04:00
Kevin Darby a080a0ccaa using sendingTime instead of transactTime 2019-10-16 14:45:13 -04:00
Kevin Darby 90321045f6 need to filter orders separately 2019-10-15 23:55:16 -04:00
Kevin Darby 8ad9e24415 interesting results 2019-10-15 23:26:10 -04:00
Kevin Darby 2008786c4e some semblance of a simulation going 2019-10-15 18:28:49 -04:00
Kevin Darby 57f8036796 jcl 2019-10-14 18:10:21 -04:00
Kevin Darby 21a304261e merge 2019-10-14 00:14:29 -04:00
Kevin Darby cefb2b2524 Merge branch 'fix' of bitbucket.org:bluetradesys/spark into vwap 2019-10-13 14:12:50 -04:00
Kevin Darby e26bdb228b Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-13 14:10:12 -04:00
Kevin Darby 1621ad03bf Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-10-13 14:09:45 -04:00
Kevin Darby 174acf1597 getting join fills 2019-10-11 18:22:53 -04:00
Kevin Darby bf32c6133a mbo parsing in c# 2019-10-11 18:18:22 -04:00
Kevin Darby fe73874b19 more analysis 2019-10-11 17:28:12 -04:00
Kevin Darby 0eb82a0d5c inching towards a simulated pnl of an arbitrary strategy 2019-10-10 18:05:14 -04:00
Kevin Darby fc2165d85d CL, ZN, ZC exhibit spookily similar histograms, ES is a little different 2019-10-10 11:04:34 -04:00
Kevin Darby a368b2ea16 factored out analyzers so we can test Join/Cover/Lean and Spreader 2019-10-09 17:46:10 -04:00
Kevin Darby 6294ef6db2 made some headway in analysis 2019-10-09 16:25:44 -04:00
Kevin Darby a9cca80c6d some corn stats 2019-10-08 17:36:16 -04:00
Kevin Darby aee9066ccf cmake 2019-10-08 15:14:44 -04:00
Kevin Darby ea3c130738 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-08 13:13:56 -04:00
Kevin Darby 193f248f06 merge deleted some of my files 2019-10-07 17:40:01 -04:00
Kevin Darby 5212fb272e Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-10-07 17:27:12 -04:00
Kevin Darby 6772c77e01 rudimentary fix orders seem to work 2019-10-07 17:18:20 -04:00
Kevin Darby 95c589f594 hooking up to run this fix server 2019-09-30 15:00:30 -04:00
Kevin Darby 699c7c5bfe Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-09-30 12:02:13 -04:00
Kevin Darby 7de82b5685 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-09-30 11:45:44 -04:00
Kevin Darby a8e9d0d7d8 ordersession and dropcopy session code complete for native orders, time to debug 2019-09-27 18:21:35 -04:00
Kevin Darby d078e73179 order session 2019-09-27 17:58:36 -04:00
Kevin Darby 4351d6e66a drop copy session code complete 2019-09-27 17:04:42 -04:00
Kevin Darby 9850d6bde0 rx-cpp requires concurrent use with prescription medicine 2019-09-27 14:29:22 -04:00
Kevin Darby d23a072ad6 rx 2019-09-26 18:27:41 -04:00
Kevin Darby 6ef0808f49 never thought I'd use rxcpp...it's truly terrible; I feel physical pain 2019-09-26 18:16:48 -04:00
Kevin Darby e1c2d8aaaf it builds, ship it 2019-09-26 12:33:42 -04:00
Kevin Darby 719fd03063 nothing builds 2019-09-26 11:45:08 -04:00
Kevin Darby d436dae7cd more fix 2019-09-24 18:22:09 -04:00
Kevin Darby f66004b465 fix port 2019-09-24 14:37:30 -04:00
Kevin Darby aa8a56787b fix fix fix 2019-09-23 18:16:05 -04:00
Kevin Darby 35a69396f1 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-09-23 17:08:56 -04:00
Kevin Darby ddfb6d1105 update externals 2019-09-23 17:03:49 -04:00
Kevin Darby 005c0f9a5d c++ fix 2019-09-23 16:59:39 -04:00
Kevin Darby cf62eae51b replay book building 2019-09-06 18:13:16 -04:00
Kevin Darby c7e49e8d21 data replay 2019-09-05 18:47:04 -04:00
Kevin Darby 8bc420190b Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-05 18:46:53 -04:00
Kevin Darby 62711a144b got some replay 2019-09-05 14:14:41 -04:00
Kevin Darby 333273b48a testing 2019-09-04 17:41:48 -04:00
Kevin Darby dd194f8ac0 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-04 17:11:44 -04:00
Kevin Darby 5992f4814d testing replay 2019-09-04 17:02:06 -04:00
Kevin Darby d67f15b3fb Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-04 15:12:02 -04:00
Kevin Darby be8635788a Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-04 14:03:41 -04:00
Kevin Darby d96355e6c4 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-03 14:36:29 -04:00
Kevin Darby 4ef2c8a2da conf changes 2019-09-03 14:35:23 -04:00
Kevin Darby 058fbe1aee utils is not in mbts any longer 2019-09-02 17:14:41 -04:00
Kevin Darby bc0f9f574a Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-02 17:05:21 -04:00
Kevin Darby f575b82ce0 almost got the replay going 2019-08-30 17:54:58 -04:00
Kevin Darby 050ea18a02 generation 2019-08-30 00:17:00 -04:00
Kevin Darby 18829279d2 working on replay from parser 2019-08-29 17:44:55 -04:00
Kevin Darby 75703d54ea getting mbo from s3 2019-08-29 12:21:01 -04:00
Kevin Darby 0a3fc1bca0 s3 2019-08-28 18:52:02 -04:00
Kevin Darby bc688ef44d parsing mbo files 2019-08-28 17:47:29 -04:00
Kevin Darby 57af89b628 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-08-28 16:47:20 -04:00
Kevin Darby 4fcc70bcf0 mbo fix parser 2019-08-27 18:06:03 -04:00
Kevin Darby cad6f2c1eb Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-26 17:57:16 -04:00
Kevin Darby c16b898d86 scaffolding for historical data 2019-08-26 17:55:26 -04:00
Kevin Darby 4f98244c3a arrival 2019-08-23 22:23:59 -04:00
Kevin Darby 8770f98fb1 more algo 2019-08-19 11:54:23 -04:00
Kevin Darby bbcf78517f self adjust 2019-08-16 11:30:36 -04:00
Kevin Darby b9a940b4e3 adjust for my size 2019-08-15 18:00:55 -04:00
Kevin Darby d8a4c1aaf7 added VolumeSlicer hierarchy, VolumeSliceAlgo and POVAlgo 2019-08-15 17:41:23 -04:00
Kevin Darby 9803cf31c8 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-15 09:36:00 -04:00
Kevin Darby 4a36177992 externals with master 2019-08-14 16:17:19 -04:00
Kevin Darby 421614198a fixed external 2019-08-14 15:45:31 -04:00
Kevin Darby 51e227e921 timeslicer 2019-08-12 22:33:26 -04:00
Kevin Darby 74948d11c5 build 2019-08-12 18:33:00 -04:00
Kevin Darby f88e1b5903 build 2019-08-12 18:30:27 -04:00
Kevin Darby 59115ce886 volume estimator 2019-08-12 18:24:29 -04:00
Kevin Darby 4b18d15421 refactor into TimeSlicer and VolumeSlicer 2019-08-12 18:07:07 -04:00
Kevin Darby 9be591c634 need a slightly different hierarchy for pov 2019-08-12 16:08:46 -04:00
Kevin Darby 462af2f1f3 make offset algo support a negative offset which means cross 2019-08-12 14:20:45 -04:00
Kevin Darby ab35c943fd working on pov 2019-08-12 13:52:57 -04:00
Kevin Darby aa36b24165 slicer params 2019-08-12 11:11:27 -04:00
Kevin Darby 547cdda3db adding aggression params to base class 2019-08-11 16:06:01 -04:00
Kevin Darby 3b77fd1a6c peg price updates 2019-08-09 18:09:31 -04:00
Kevin Darby a8110c9a8d added peg algo to support vwap/twap 2019-08-09 17:58:09 -04:00
Kevin Darby b6f80d6a4a twap working ok 2019-08-09 14:52:25 -04:00
Kevin Darby 18489e64a2 twapping 2019-08-08 18:16:00 -04:00
Kevin Darby a3d2df9ba2 . 2019-08-08 16:44:35 -04:00
Kevin Darby 314700e2e7 residual quantity 2019-08-08 16:09:06 -04:00
Kevin Darby 461b6c15d6 twap bugs 2019-08-08 15:25:00 -04:00
Kevin Darby d23ac7e8ef allow generic order vm to reflect one level deep into protos to generate a gui; will need john's help on a datetimepicker 2019-08-08 14:54:34 -04:00
Kevin Darby 68c0179465 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-08 12:20:15 -04:00
Kevin Darby 8bc1455813 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-08 11:07:40 -04:00
Kevin Darby ecaa04ecf8 factor 2019-08-08 09:59:14 -04:00
Kevin Darby 44e3070544 add TimeSlicer 2019-08-08 00:05:41 -04:00
Kevin Darby 1815950835 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-07 17:31:50 -04:00
Kevin Darby 8762fbf7ce added twap to algo types 2019-08-06 18:20:10 -04:00
Kevin Darby 0459171d6e moved algo types to config 2019-08-06 18:15:47 -04:00
Kevin Darby fcdf72c5d9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-06 16:57:36 -04:00
Kevin Darby f5caf0b7ba twap bones 2019-08-06 16:57:02 -04:00
Kevin Darby a0f2673b3a basic timing structure down; maybe all these derive from twap 2019-08-05 23:51:28 -04:00
Kevin Darby ba3439b049 twap 2019-08-05 18:21:03 -04:00
Kevin Darby ae4d7f719b Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-02 17:57:21 -04:00
Kevin Darby 64916f6bfc Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-02 17:38:30 -04:00
Kevin Darby 1a9dd1c37a cleaning up this proto 2019-08-01 17:40:21 -04:00
Kevin Darby d77925c568 Merge branch 'prorata' of bitbucket.org:bluetradesys/spark into vwap 2019-08-01 17:11:38 -04:00
Kevin Darby f1f1b68ffb arrival 2019-07-03 22:14:39 -04:00
Kevin Darby 835f453f03 Merge branch 'prorata' of bitbucket.org:bluetradesys/spark into vwap 2019-07-03 15:17:09 -04:00
Kevin Darby 0eb9518485 didn't get far 2019-06-13 23:41:30 -04:00
Kevin Darby b5c70fc34e added price to slice 2019-06-11 23:56:11 -04:00
Kevin Darby a1dba1d7d0 historicaldataservice 2019-06-11 23:12:20 -04:00
Kevin Darby efdea5256f algoflow will probably not be in python 2019-06-11 21:07:55 -04:00
Kevin Darby 5df1e5f383 py notes 2019-06-09 22:50:46 -04:00
Kevin Darby 99750da31d beginnings of python driver for algo development. goal is to allow visual / interactive development environment for
more complex algos.  Will also offer backtesting of bts algos over MBO data.
2019-06-09 22:47:51 -04:00
Kevin Darby 49247dba3f externals 2019-06-09 21:58:11 -04:00
Kevin Darby a25257cb33 update externals for pybind 2019-06-08 23:33:43 -04:00
Kevin Darby b1c180d663 Merge branch 'prorata' of bitbucket.org:bluetradesys/spark into vwap 2019-06-07 16:01:49 -04:00
Kevin Darby 6afb16abcb bones for bluex orders 2019-06-07 15:53:25 -04:00
Kevin Darby 995fa3df35 . 2019-06-06 23:34:25 -04:00
Kevin Darby 18eb28dfd2 waps 2019-06-06 22:08:03 -04:00
Kevin Darby 99cdfadf8d bones for vwap, twap, arrivalprice, pov 2019-06-05 23:30:52 -04:00
Kevin Darby feab3cbb89 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-01-22 16:10:16 -05:00
Kevin Darby 1f7c1be194 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2018-12-28 13:51:59 -05:00
Kevin Darby f78f18b3b4 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2018-12-20 16:20:19 -05:00
Kevin Darby 6e7fa91718 ready for testing 2018-12-14 10:39:34 -05:00
Kevin Darby 8cac406547 fix error handling 2018-12-12 17:33:25 -05:00
Kevin Darby 0d83f28cbc fix lives 2018-12-12 14:26:03 -05:00
Kevin Darby 941f4bf837 Merge branch 'cleanup_mdclient' of bitbucket.org:bluetradesys/spark into fix 2018-12-11 23:05:35 -05:00
Kevin Darby 8bbe9cfd98 Merge branch 'master' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-06 12:56:02 -05:00
Kevin Darby ba633532eb hit a wall here with MDClient, going to refactor 2018-12-06 12:55:21 -05:00
Kevin Darby 7bf50415ca nearly ready for testing 2018-12-05 17:55:06 -05:00
Kevin Darby edbaa9c435 looks like we have dropcopy 2018-12-05 15:35:49 -05:00
Kevin Darby 7789f39c9d handle some messages 2018-12-04 18:20:23 -05:00
Kevin Darby 6e188b4f73 shaping up 2018-12-04 16:37:41 -05:00
Kevin Darby 33e8ff58c5 Merge branch 'master' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-04 09:42:30 -05:00
Kevin Darby 24ae03a9f8 access permissions api so fix session can report fills and orders 2018-12-03 18:19:20 -05:00
Kevin Darby 5fe0335e1a Merge branch 'master' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-03 17:02:34 -05:00
Kevin Darby 6cbf707094 Merge branch 'sdk_doc' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-03 16:06:58 -05:00
Kevin Darby 7e4ad55d9d established a session 2018-12-03 15:32:45 -05:00
Kevin Darby 35a0074014 bts fix44 adapter 2018-12-03 14:49:35 -05:00
Kevin Darby b8dd5c119d Merge branch 'sdk_doc' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-03 10:41:34 -05:00
Kevin Darby 8ada73aa64 Merge branch 'sdk_doc' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-11-30 18:37:39 -05:00
Kevin Darby a16ff30873 lets see how hard this is 2018-11-30 18:32:51 -05:00
402 changed files with 117531 additions and 5247 deletions

No files matched your search

+4
View File
@@ -1,5 +1,7 @@
nohup.out
build/
build
cme_data/
cmake-build-debug/
.idea/
.dir-locals.el
@@ -34,3 +36,5 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
/build
/analysis_data
+130 -8
View File
@@ -46,7 +46,7 @@
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
@@ -98,6 +98,46 @@
"externalConsole": true,
"requireExactSource": false
},
{
"name": "sparkcmd Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"historical",
"--rstart", "11/20/2019",
"--rend", "11/29/2019",
"--rsymbol", "NG",
"--rsecuritytype", "Synthetic",
"--post_process_db", "NG_201911_W4",
"--post_process_db_host", "bigblue",
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "sparkcmd simulation",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"historical",
"--rstart", "11/26/2019",
"--rend", "11/26/2019",
"--rsymbol", "ZN",
"--rsecuritytype", "Synthetic",
"--algo_security", "ZNH0;ZNZ9-ZNH0;ZNZ9",
"--algo_type", "RoundTrip",
"--algo_params", "size=10;tick_size=0.015625,0.0078125,0.015625;hedge_factors=1,1,-1",
"--skip_preanalysis", "true",
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Excel Launch",
"type": "clr",
@@ -131,6 +171,18 @@
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": ".NET Framework Attach",
"type": "clr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "Spreader Tests",
"type": "cppdbg",
@@ -342,15 +394,19 @@
"justMyCode": true
},
{
"name": ".NET Core Remote Launch",
"name": "sparkcmd vsdbg (c#)",
"type": "coreclr",
"request": "launch",
"program": "/usr/bin/dotnet",
"program": "sparkcmd",
"args": [
"sparkcmd.dll",
"test",
"test_join_create_and_takeout_market"
"historical",
"--rstart", "11/20/2019",
"--rend", "11/20/2019",
"--rsymbol", "CL",
"--rsecuritytype", "Spread",
"--post_process_db", "mbo_history",
],
"env": {"LD_LIBRARY_PATH" : "."},
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server",
"pipeTransport": {
@@ -361,11 +417,77 @@
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": false
},
},
{
"name": "sparkcmd gdb (c++)",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkcmd",
"args": [
"historical",
"--rstart", "10/21/2019",
"--rend", "10/21/2019",
"--rsymbol", "CL",
"--rsecuritytype", "Future",
"--algo_security", "CLX9",
"--algo_type", "TWAP",
"--algo_params", "'size=100;profit_ticks=1;tick_size=1,1,1;;hedge_factors=1,1,-1'",
"--skip_preanalysis", "true",
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"preLaunchTask": "",
"logging": {
"engineLogging": false
},
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "enable pretty-printer",
"ignoreFailures": true
},
{
"description": "Skip STL",
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
"ignoreFailures": true
},
{
"description": "LD LIBRARY",
"text": "set env LD_LIBRARY_PATH=.",
"ignoreFailures": false
}
],
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
"name": "Jupyter notebook",
"type": "coreclr",
"request": "launch",
"program": "jupyter",
"args": ["notebook", "--notebook-dir=c:\\code\\bts\\spark\\src\\client\\spark_historical\\Notebooks"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
},
]
}
+11 -1
View File
@@ -48,6 +48,7 @@ else ()
add_subdirectory(doc/user)
add_subdirectory(src/sdk)
add_subdirectory(src/data_files)
add_subdirectory(src/client)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
@@ -59,7 +60,16 @@ else ()
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
COMMENT "Creating server runtime layout"
DEPENDS sparkd data_files_dir)
DEPENDS sparkd data_files_dir spark_client_dir)
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
add_custom_command(TARGET server_layout POST_BUILD
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
else()
add_custom_command(TARGET server_layout POST_BUILD
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/publish/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
endif()
endif()
+5
View File
@@ -6,9 +6,14 @@ insert_anchor_links = "right"
+++
## 2.14.0
### User Visible Changes
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
- Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity.
- Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator.
- Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites
### Bug Fixes
- Fixed an issue where market transition were not observed by IAlgoOrder
- Fixed a bug in error reporting
- Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure
## 2.13.0
### User Visible Changes
+51
View File
@@ -23,7 +23,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\sp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_fix", "src\client\spark_fix\spark_fix.csproj", "{D8031C13-7BEA-430A-BB60-40927D15CFC3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_historical", "src\client\spark_historical\spark_historical.csproj", "{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}"
EndProject
Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
@@ -219,6 +226,50 @@ Global
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|Any CPU.ActiveCfg = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.ActiveCfg = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.Build.0 = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x86.ActiveCfg = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|Any CPU.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x86.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.Build.0 = Release|x64
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+123
View File
@@ -0,0 +1,123 @@
drop table if exists msgs ;
create table msgs (
MsgID Int4,
TradeDate varchar,
SeqNum Int4,
SendingTime varchar,
TransactTime varchar,
MatchEventIndicator varchar,
NoMDEntries Int2,
NoOrderIDEntries Int2,
SendingTime_UnixTS Int8,
TransactTime_UnixTS Int8
);
drop table if exists books ;
create table books (
MsgID Int4,
SendingTime varchar,
TransactTime varchar,
SeqNum Int4,
bid_p_0 Real,
bid_sz_0 Int4,
bid_p_1 Real,
bid_sz_1 Int4,
bid_p_2 Real,
bid_sz_2 Int4,
bid_p_3 Real,
bid_sz_3 Int4,
bid_p_4 Real,
bid_sz_4 Int4,
bid_p_5 Real,
bid_sz_5 Int4,
bid_p_6 Real,
bid_sz_6 Int4,
bid_p_7 Real,
bid_sz_7 Int4,
bid_p_8 Real,
bid_sz_8 Int4,
bid_p_9 Real,
bid_sz_9 Int4,
Symbol Varchar,
ask_p_0 Real,
ask_sz_0 Int4,
ask_p_1 Real,
ask_sz_1 Int4,
ask_p_2 Real,
ask_sz_2 Int4,
ask_p_3 Real,
ask_sz_3 Int4,
ask_p_4 Real,
ask_sz_4 Int4,
ask_p_5 Real,
ask_sz_5 Int4,
ask_p_6 Real,
ask_sz_6 Int4,
ask_p_7 Real,
ask_sz_7 Int4,
ask_p_8 Real,
ask_sz_8 Int4,
ask_p_9 Real,
ask_sz_9 Int4,
SendingTime_UnixTS Int8,
TransactTime_UnixTS Int8
);
drop table if exists trades;
create table trades (
MsgID Int4,
Symbol varchar,
SendingTime varchar,
TransactTime varchar,
TradeDate varchar,
MDEntryPx Real,
MDnetrySize Int4,
NumberOfOrders Int4,
AgressorSide Int4,
MDTradeEntryID Int4,
AgressorOID varchar,
RestingOID varchar,
PartialQty Int4,
SendingTime_UnixTS Int8,
TransactTime_UnixTS Int8
);
drop table if exists orders;
create table orders (
MsgID Int4,
Symbol varchar,
SendingTime varchar,
TransactTime varchar,
MDEntryPx Real,
OrderID varchar,
MDDisplayQty Int4,
MDOrderPriority Int8,
OrderUpdateAction Int4,
Side Int4,
SendingTime_UnixTS Int8,
TransactTime_UnixTS Int8
);
-- create unique index msg_msg_id on msgs msgid;
-- create view ranges as (
-- select
-- sendingtime,
-- transacttime,
-- bid_p_0 price,
-- case when lag(bid_p_0) over (order by SendingTime) = bid_p_0 then null else 1 end seq
-- from
-- u0v0_books);
-- create view groups as (
-- select
-- sendingtime,
-- transacttime,
-- price,
-- sum(seq) over (order by SendingTime) grp
-- from
-- ranges);
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
DB=$1
TABLE=$2
FILE=$3
zcat $FILE | psql -U postgres -d $DB -c "copy $TABLE from STDIN with delimiter as ',' CSV HEADER"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
docker exec -it postgres psql -U postgres "$@"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
#docker run --net host -e PGADMIN_DEFAULT_EMAIL=bts -e PGADMIN_DEFAULT_PASSWORD=BT5u53r dpage/pgadmin4
docker run \
--rm \
-d \
-p 8000:80 \
-e PGADMIN_DEFAULT_EMAIL=bts \
-e PGADMIN_DEFAULT_PASSWORD=BT5u53r \
--name pgadmin \
dpage/pgadmin4
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SRC_ROOT=$DIR/../../..
DATA_DIR=`readlink -f $SRC_ROOT/analysis_data`
DB_DIR=$DATA_DIR/db
mkdir -p $DB_DIR
docker run \
--rm \
-d \
--name postgres \
--net host \
-v $DB_DIR:/var/lib/postgresql/data \
-v /home/$USER:/home/$USER \
-v $DATA_DIR:$DATA_DIR \
-v /etc/group:/etc/group \
-v /run/user/:/run/user/ \
-u $UID \
-t postgres \
postgres
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SRC_ROOT=$DIR/../../
DATE=$1
DB=md_$DATE
$DIR/db/psql.sh -c "drop database if exists $DB"
$DIR/db/psql.sh -c "create database $DB"
$DIR/db/psql.sh -d md_$DATE -f $DIR/db/create_db.sql
PROCESSED_DATA=$SRC_ROOT/analysis_data/processed/$DATE
ALL_COMMANDS=""
for TABLE in trades orders msgs books
do
for ITEM in $PROCESSED_DATA/*.$TABLE.gz
do
ALL_COMMANDS+="docker exec -t postgres /bin/bash -c \""
ALL_COMMANDS+="$DIR/db/import_csv.sh $DB $TABLE $ITEM"
ALL_COMMANDS+="\"\n"
done
done
echo -e $ALL_COMMANDS | parallel --will-cite
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DATE=$1
SRC_ROOT=$DIR/../../
SPARK_BIN_ROOT=`readlink -f $SRC_ROOT/build`/ninja_release/server
RAW_DATA=$SRC_ROOT/analysis_data/raw/$DATE
PROCESSED_DATA=$SRC_ROOT/analysis_data/processed/$DATE
mkdir -p $PROCESSED_DATA
rm $PROCESSED_DATA/* >/dev/null
ALL_COMMANDS=""
for IN_PATH in $RAW_DATA/*.gz
do
BASE_NAME=`basename $IN_PATH`
BASE_NAME_STEM=${BASE_NAME%.*}
for CMD in trades orders msgs books
do
OUT_PATH=$PROCESSED_DATA/$BASE_NAME_STEM.$CMD.gz
ALL_COMMANDS+="(cd $SPARK_BIN_ROOT && zcat $IN_PATH | ./sparkd parsehistoricalfix $CMD | gzip >$OUT_PATH)\n"
done
done
echo -e $ALL_COMMANDS | parallel --will-cite
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
d=$1
end=${2-`date +%Y%m%d`}
until [[ $d > $end ]]; do
echo "$d"
d=$(date -d "$d + 1 day" +%Y%m%d)
done
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DATE=${1-`date -d "yesterday" +%Y%m%d`}
$DIR/sync.sh $DATE &&
$DIR/parse.sh $DATE &&
$DIR/load.sh $DATE
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DATE=$1
SRC_ROOT=$DIR/../../
RAW_DATA_ROOT=$SRC_ROOT/analysis_data/raw/$DATE
mkdir -p $RAW_DATA_ROOT
AWS_ACCESS_KEY_ID=AKIAIFIGOOWGBLEMO3PQ AWS_SECRET_ACCESS_KEY=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 AWS_REGION=us-east-1 aws s3 sync s3://cme-bts-datamine/mbo/mbo/$DATE/ $RAW_DATA_ROOT
+19
View File
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.5)
project(spark_client)
include(${CMAKE_SOURCE_DIR}/tools/safe_dep_glob.cmake)
file(GLOB_RECURSE spark_client_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cs)
file(GLOB_RECURSE spark_client_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.csproj)
file(GLOB_RECURSE spark_ui_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.cs)
file(GLOB_RECURSE spark_ui_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.csproj)
list(REMOVE_ITEM spark_ui_sources ${spark_client_sources} )
list(REMOVE_ITEM spark_ui_csproj ${spark_client_csproj} )
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/../
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh
ARGS ${CMAKE_BUILD_TYPE} ${CMAKE_BINARY_DIR}
COMMENT "Building sparkcmd in netcore3"
DEPENDS libspark ${spark_client_sources} ${spark_client_csproj} ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh)
add_custom_target(spark_client_dir ALL DEPENDS ${CMAKE_BINARY_DIR}/../)
@@ -5,6 +5,7 @@
<SparkRoot>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\..))</SparkRoot>
<SourceRoot>$([System.IO.Path]::GetFullPath($(SparkRoot)\src))</SourceRoot>
<Externals>$(SparkRoot)\externals\windows</Externals>
<Externals Condition=" '$(OS)' != 'Windows_NT' ">$(SparkRoot)/externals/linux</Externals>
<DepotName>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath($(SparkRoot)))))</DepotName>
<ErrorReport>None</ErrorReport>
<Prefer32Bit>False</Prefer32Bit>
@@ -22,17 +23,20 @@
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
</PropertyGroup>
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
<ItemGroup>
<AvailableItemName Include="Proto2">
<Targets>CompileProto3</Targets>
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto2</Targets>
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto2Linux</Targets>
</AvailableItemName>
<AvailableItemName Include="Proto3">
<Targets>CompileProto3</Targets>
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto3</Targets>
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto3Linux</Targets>
</AvailableItemName>
<AvailableItemName Include="Proto3Excel">
<Targets>CompileProto3Excel</Targets>
<Targets Condition=" '$(OS)' == 'Windows_NT' ">CompileProto3Excel</Targets>
</AvailableItemName>
</ItemGroup>
@@ -43,6 +47,14 @@
<protogen>$(Externals)\bin\protogen.exe</protogen>
</PropertyGroup>
<PropertyGroup Label="ProtoBuf" Condition=" '$(OS)' != 'Windows_NT' ">
<ProtocPluginBase>$(SparkRoot)/src/lib/api/service_compiler</ProtocPluginBase>
<ProtoInclude>$(Externals)/include</ProtoInclude>
<protoc>$(Externals)/bin/protoc</protoc>
<protoc_rename>$(Externals)/bin/lowercase_files.sh</protoc_rename>
<protogen>$(Externals)/bin/protogen</protogen>
</PropertyGroup>
<ItemDefinitionGroup>
<Proto2>
</Proto2>
@@ -3,7 +3,7 @@
<Target Name="CompileProto2"
Condition="'@(Proto2)'!=''"
Condition="'@(Proto2)'!='' AND '$(OS)' == 'Windows_NT' "
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
Outputs="$(GenDir)\proto2\%(Proto2.filename).cs"
AfterTargets="PreBuildEvent">
@@ -28,7 +28,7 @@
passes wipes out the additional compile includes that we add.
-->
<Target Name="CompileProto3" AfterTargets="GetAssemblyVersion"
Condition="'@(Proto3)'!=''"
Condition="'@(Proto3)'!='' AND '$(OS)' == 'Windows_NT' "
Inputs="$(protoc);
%(Proto3.Identity);$(ProtocPluginBase)\plugin.py;
$(ProtocPluginBase)\templates\c#\client.pb.cs;
@@ -58,7 +58,7 @@
</Target>
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
Condition="'@(Proto3Excel)'!=''"
Condition="'@(Proto3Excel)'!='' AND '$(OS)' == 'Windows_NT' "
Inputs="$(protoc);
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
@@ -82,12 +82,74 @@
</ItemGroup>
</Target>
<Target Name="CompileProto2Linux"
Condition="'@(Proto2)'!='' AND '$(OS)' != 'Windows_NT' "
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
Outputs="$(GenDir)/proto2/%(Proto2.filename).cs"
AfterTargets="PreBuildEvent">
<PropertyGroup>
<SourceRelativeFile>$([System.String]::Copy('%(proto2.fullpath)').Replace($(SourceRoot), ''))</SourceRelativeFile>
<SourceRelativeDir>$([System.IO.Path]::GetDirectoryName($(SourceRelativeFile)))</SourceRelativeDir>
<WorkingDirectory>$(GenDir)/proto2</WorkingDirectory>
</PropertyGroup>
<MakeDir Directories="$(WorkingDirectory)" />
<Copy SourceFiles="%(Proto2.FullPath)" DestinationFolder="$(WorkingDirectory)" SkipUnchangedFiles="True" UseHardLinksIfPossible="True" />
<Error Text="protogen.exe does not exist. If you forgot to clone with externals try: git submodule update --init --recursive" Condition="!Exists($(protogen))" />
<Message Importance="High" Text="Doing Proto2 %(proto2.filename)" />
<Exec Command="PYTHONPATH=$(Externals)/python $(Protogen) %(Proto2.filename)%(proto2.extension) --csharp_out=. -I. -I$(SourceRoot) -I$(Externals)/include"
WorkingDirectory="$(WorkingDirectory)" />
<ItemGroup>
<Compile Include="$(WorkingDirectory)/%(Proto2.filename).cs" />
</ItemGroup>
</Target>
<Target Name="CompileProto3Linux" AfterTargets="GetAssemblyVersion"
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' "
Inputs="$(protoc);
%(Proto3.Identity);$(ProtocPluginBase)/plugin.py;
$(ProtocPluginBase)/templates/c#/client.pb.cs;
$(ProtocPluginBase)/templates/c#/server.pb.cs;
$(ProtocPluginBase)/templates/c#/immutable.pb.cs;"
Outputs="%(Proto3.OutCS);%(Proto3.OutCSServer);%(Proto3.OutCSClient);%(Proto3.OutCSImmutable)">
<MakeDir Directories="%(Proto3.OutBase)" />
<MakeDir Directories="$(IntermediateOutputPath)/gen" />
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
<Exec Command="
PYTHONPATH=$(Externals)/python $(protoc) \
%(Proto3.FullPath) \
--error_format=gcc \
--proto_path=$(protoinc):$(SourceRoot) \
--plugin=&apos;protoc-gen-service=$(ProtocPluginBase)/plugin.py&apos; \
--service_out=lang=c#,strip_out_path=1:%(Proto3.OutBase) \
--csharp_out=%(CompileOptions)%(Proto3.OutBase)"
WorkingDirectory="%(relativedir)" />
<!-- <Exec Command="$(protoc_rename) %(Proto3.OutBase)" /> -->
<ItemGroup>
<Compile Include="%(Proto3.OutCS)" />
<Compile Include="%(Proto3.OutCSClient)" />
<Compile Include="%(Proto3.OutCSServer)" />
<Compile Include="%(Proto3.OutCSImmutable)" />
</ItemGroup>
</Target>
<!-- <Target Name="RenameProto3Linux" AfterTargets="CompileProto3Linux"
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' ">
<Move SourceFiles="$(OutBase)\Apicore.cs" DestinationFiles="$(OutBase)\apicore.cs" />
<Move SourceFiles="$(OutBase)\Apioptions.cs" DestinationFiles="$(OutBase)\apioptions.cs" />
<Move SourceFiles="$(OutBase)\Co2.cs" DestinationFiles="$(OutBase)\co2.cs" />
</Target> -->
<Target Name="AfterClean" AfterTargets="Clean">
<Delete Files="%(Proto3.OutCS)" />
<Delete Files="%(Proto3.OutCSClient)" />
<Delete Files="%(Proto3.OutCSServer)" />
<Delete Files="%(Proto3.OutCSImmutable)" />
<Delete Files="$(GenDir)\proto2\*" />
<Delete Files="$(GenDir)/proto2/*" />
<Delete Files="%(Proto3Excel.OutCSExcel)" />
</Target>
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
#$1 = Debug / Release (CMAKE_BUILD_TYPE), $2 = CMAKE_BINARY_DIR
cd "$(dirname "$0")"
echo $PWD dotnet build -c $1 ./sparkcmd/
dotnet build -c $1 ./sparkcmd/
if [ "$1" == "Release" ]
then
dotnet publish -c $1 ./sparkcmd/
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64/publish
fi
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64
-2
View File
@@ -40,7 +40,6 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
HamburgerMenuGeometryKey = nameof(HamburgerMenuGeometryKey),
HornGeometryKey = nameof(HornGeometryKey),
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
@@ -173,7 +172,6 @@ namespace mbts.ui {
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
public static ImageSource Gear => create_icon(GearGeometryKey);
public static ImageSource HamburgerMenu => create_icon(HamburgerMenuGeometryKey);
public static ImageSource Horn => create_icon(HornGeometryKey);
public static ImageSource Import => load_resource_and_freeze("Import");
public static ImageSource Key => create_icon(KeyGeometryKey);
@@ -272,10 +272,6 @@
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.HamburgerMenuGeometryKey}">
M2.384186E-06,24L32.000004,24 32.000004,26 2.384186E-06,26z M0,11.999998L32,11.999998 32,13.999998 0,13.999998z M2.384186E-06,0L32.000004,0 32.000004,2 2.384186E-06,2z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.HornGeometryKey}">
M4.0499969,7.0870913C2.9519978,7.0870913 2.0249984,8.4780906 2.0249987,10.125091 2.0249984,11.771091 2.9519978,13.162091 4.0499969,13.162091 5.1479961,13.162091 6.0749954,11.771091 6.0749955,10.125091 6.0749954,8.4780906 5.1479961,7.0870913 4.0499969,7.0870913z M29.975016,3.316155C23.883831,8.2016557,11.829888,8.9691842,8.1184387,9.0896781L8.0151367,9.0928462 8.0175586,9.1059306C8.0716033,9.4352315 8.0999939,9.7760909 8.0999942,10.125091 8.0999939,10.473966 8.0716033,10.814716 8.0175586,11.143921L8.0098858,11.185364 8.193316,11.195448C12.163719,11.420399,23.994581,12.458921,29.975016,17.005057z M31.176758,0.00019051835C31.280359,-0.0022594665 31.391857,0.01889403 31.514975,0.065146674 31.908983,0.21413228 31.999985,0.59114302 31.999985,1.0121596L31.999985,19.236969C31.999985,19.653958 31.913988,20.028956 31.525991,20.179955 31.405998,20.226952 31.450981,20.250023 31.32699,20.250023 31.050991,20.250023 30.778992,20.13601 30.583986,19.925928 25.229074,14.140572 9.6001122,13.269665 7.2865356,13.172559L7.2813506,13.172345 7.1739591,13.343056C6.4304952,14.468533 5.3060585,15.187091 4.0499969,15.187091 1.8169985,15.187091 0,12.916091 0,10.125091 0,7.3330911 1.8169985,5.0620912 4.0499969,5.0620912 5.3060585,5.0620912 6.4304952,5.78065 7.1739591,6.9063042L7.2852713,7.0832756 7.3087114,7.0828188C9.6077337,7.036146 25.141474,6.4939421 30.567997,0.34315929 30.75913,0.12521249 30.948835,0.005580858 31.176758,0.00019051835z
</sys:String>
@@ -1,13 +0,0 @@
using mbts.spark;
namespace mbts.ui.spark {
readonly struct MarketViewCell {
public readonly MarketViewVM.Row row;
public readonly MarketViewView.Columns column;
public MarketViewCell(MarketViewVM.Row row, MarketViewView.Columns column) {
this.row = row;
this.column = column;
}
}
}
@@ -5,7 +5,6 @@ using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -19,7 +18,7 @@ using System.Windows.Input;
namespace mbts.spark {
partial class MarketViewVM {
public class Row : DisposableViewModelBase, ISecurityMDRow, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly AnimationManager _animation = new AnimationManager();
@@ -28,6 +27,7 @@ namespace mbts.spark {
readonly string _security_text, _security_key_text;
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
readonly Property<bool> _last_price_from_spread;
readonly CellProperty<int> _position;
readonly CellProperty<uint> _last_size;
readonly CellProperty<ulong> _volume;
readonly CellProperty<bool> _inside_bids, _inside_asks;
@@ -37,18 +37,17 @@ namespace mbts.spark {
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
double _day_position, _raw_day_position;
double _raw_day_position;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
bool _is_visible;
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) {
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
_bus = bus;
security_md = smd;
_orders_model = orders_model;
_security_text = smd.security.ToString();
_security_key_text = security_key.ToString();
string format_price(double price) => security.ContractSpec.format(price);
_position = new CellProperty<int>(this, nameof(position), 0);
_bid_price = backing(nameof(bid_price), double.NaN);
_ask_price = backing(nameof(ask_price), double.NaN);
_last_price = backing(nameof(last_price), double.NaN);
@@ -92,17 +91,9 @@ namespace mbts.spark {
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
_dispose = Disposable.Create(
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
bool day_pos_dirty = false;
var new_day_pos = t.state.Values.Sum(p => p.Item2.day_pos());
if (new_day_pos != _day_position) {
if (!double.IsNaN(new_day_pos) && !double.IsNaN(_day_position))
_animation.animate_position(new_day_pos > _day_position);
_day_position = new_day_pos;
RaisePropertyChanged(nameof(day_position));
day_pos_dirty = true;
}
if (day_pos_dirty | Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
raise_invalidated();
}),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
@@ -114,20 +105,10 @@ namespace mbts.spark {
public event Action invalidated;
public SecurityMD security_md { get; }
public SecurityImmutable security => security_md.security;
public uint security_key => security_md.security.Id;
SecurityMD ISecurityMDRow.md => security_md;
public bool is_visible {
get => _is_visible;
set => Set(ref _is_visible, value);
}
#region Bound Properties
public bool inside_bids => _inside_bids.value;
public bool inside_asks => _inside_asks.value;
public double day_position => _day_position;
public int position => _position.value;
public double raw_day_position => _raw_day_position;
public double last_price => _last_price.Value;
public bool last_price_from_spread => _last_price_from_spread.Value;
@@ -177,30 +158,29 @@ namespace mbts.spark {
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
#endregion
MDConsumerHandle _md => security_md.md;
public void raise_invalidated() => invalidated?.Invoke();
public void refresh() {
bool dirty = false;
if (_md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
_bid_price.set(b.price);
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
_ask_price.set(a.price);
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
_refresh_inside_working_orers(ref dirty);
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
}
if (_md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
_last_price.set(ps.price);
_last_price_from_spread.set(from_spread);
_last_size.on_next_distinct(ps.size, ref dirty);
_volume.on_next_distinct(v, ref dirty);
}
dirty |=
_settlement.set_new(_md.settlement_price) |
_session_low.set_new(_md.session_low) |
_session_high.set_new(_md.session_high) |
_day_change.set_new(last_price - settlement);
dirty |= _settlement.set_new(md.md.settlement_price);
dirty |= _session_low.set_new(md.md.session_low);
dirty |= _session_high.set_new(md.md.session_high);
dirty |= _day_change.set_new(last_price - settlement);
_volume.on_next_distinct(md.md.total_volume, ref dirty);
if (dirty)
invalidated?.Invoke();
@@ -226,7 +206,7 @@ namespace mbts.spark {
security_key.GetHashCode();
#region Show Windows
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, security_md);
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
void _show_order_book(MarketViewView.Columns col) {
switch (col) {
@@ -295,11 +275,14 @@ namespace mbts.spark {
}
void _refresh_orders(ref bool dirty) {
_position.on_next_distinct(_order_summarizer.position, ref dirty);
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
_refresh_inside_working_orers(ref dirty);
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
RaisePropertyChanged(
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
// This might be part of the filter funk?
@@ -309,11 +292,6 @@ namespace mbts.spark {
nameof(working_asks_text)
);
}
void _refresh_inside_working_orers(ref bool dirty) {
_inside_bids.on_next_distinct(!double.IsNaN(working_best_bid) && OrderDataHelper.PriceComparer.Compare(working_best_bid, bid_price) >= 0, ref dirty);
_inside_asks.on_next_distinct(!double.IsNaN(working_best_ask) && OrderDataHelper.PriceComparer.Compare(working_best_ask, ask_price) <= 0, ref dirty);
}
#endregion
class MyOrderSummarizer {
@@ -389,4 +367,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -1,30 +1,15 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.persist;
using mbts.spark.filters;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using static bts.shell.MarketViewWindowFactory;
@@ -32,61 +17,21 @@ using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.MarketViewView;
namespace mbts.spark {
/// <summary>
/// Viewmodel class designed for <see cref="ui.spark.MarketViewView"/>
/// </summary>
partial class MarketViewVM : DisposableViewModelBase, IHandleRowSelection {
static readonly IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>().ToReadOnlyList();
static readonly IComparer<SecurityMD> _sec_md_key_comparer = Comparer<uint>.Default.wrap(SecurityMD.key_fn);
static readonly IComparer<Row> _row_key_comparer = _sec_md_key_comparer.wrap<SecurityMD, Row>(r => r.security_md);
static readonly string _security_delimiter = $" {SpecialCharacters.Dot} ";
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly ObservableCollection<Row> _rows = new ObservableCollection<Row>();
readonly IList<SecurityMD> _wrapped_rows;
readonly ListCollectionView _rows_view;
readonly Dictionary<uint, Row> _cache = new Dictionary<uint, Row>();
readonly Property<Row> _current_row;
readonly DeferrableActionWrapper _refresh_filter;
Func<Row, bool> _filter;
IComparer<Row> _comparer;
Color _selection_highlight;
IWindowSettingsManager<GridSettings> _settings_manager;
/// <summary>
/// Initializes an instance of the <see cref="ui.spark.MarketViewView"/> class.
/// </summary>
/// <param name="bus"></param>
/// <param name="orders_model"></param>
/// <param name="security_mds_source"></param>
/// <param name="use_mouse_state_transitions">Indicates whether or not hyperlink cells should use visual states to animate mouse over (seems to be slower than style triggers, should investigate to be certain).</param>
public MarketViewVM(
IBus bus,
OrdersModel orders_model,
IObservable<TableUpdate<SecurityMD, uint>> security_mds_source,
bool use_mouse_state_transitions = false) {
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
// securities_connectable should NOT be connected already.
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
_bus = bus;
_orders_model = orders_model;
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
// rows
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
var r = new Row(_bus, s, _orders_model);
r.is_visible = _filter == null || _filter(r);
return r;
});
_current_row = backing<Row>(nameof(current_row));
_rows_view = new ListCollectionView(_rows) {
Filter = obj => obj is Row r && r.is_visible,
IsLiveFiltering = true
};
_rows_view.LiveFilteringProperties.Add(nameof(Row.is_visible));
// Important: these selected_security/md wrappers should be instantiated in the constructor (rather than lazily),
// to prevent situations where callers subscribe up to the selected_rows.CollectionChanged event before the wrappers do.
// This way, the wrappers are protected from unintended side effects of the foreign callbacks, as they will always receive CollectionChange notifications first.
selected_securities = selected_rows.SelectObservable(r => r.security);
selected_security_mds = selected_rows.SelectObservable(r => r.security_md);
_settings_manager = settings_manager;
// Columns
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
const double working_orders_col_width = 105;
@@ -94,55 +39,24 @@ namespace mbts.spark {
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax);
add_hl_col(
Columns.Position,
"Day Pos",
new Binding(nameof(Row.day_position)),
nameof(Row.day_position),
day_position_filter,
DataGridFilterKnownTemplates.IntMinMax,
DefaultColumnWidths.Size,
tooltip: $"Show {_fills_title(Columns.Position).desc}");
add_hl_col(
Columns.RawDayPosition,
"Raw Day Pos",
new Binding(nameof(Row.raw_day_position)) {
Converter = ValueConverter.create<double, string>(SparkUtils.fmt_position)
},
nameof(Row.raw_day_position),
raw_day_position_filter,
DataGridFilterKnownTemplates.DoubleMinMax,
DefaultColumnWidths.Size,
tooltip: $"Show {_fills_title(Columns.RawDayPosition).desc}");
add_hl_working_orders_col(
Columns.WorkingBids,
working_col_header,
nameof(Row.working_bids_text),
nameof(Row.working_best_bid),
working_bid_volume_filter,
BTSColors.MarketData.bid);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
add_hl_merged_size_col(Columns.BidMergedSize, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), bid_merged_size_filter, BTSColors.MarketData.bid);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask);
add_hl_merged_size_col(Columns.AskMergedSize, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), ask_merged_size_filter, BTSColors.MarketData.ask);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
add_hl_working_orders_col(
Columns.WorkingAsks,
working_col_header,
nameof(Row.working_asks_text),
nameof(Row.working_best_ask),
working_ask_volume_filter,
BTSColors.MarketData.ask);
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true);
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
@@ -151,32 +65,9 @@ namespace mbts.spark {
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
// row vm filter
// Althoguh securities are filtered at the row 'model' level (SecurityMD), the column base filters have to be applied at the row vm level,
// because they access market data, which is polled periodically and cached in the row vm.
_refresh_filter = new DeferrableActionWrapper(() => _filter_changed?.Invoke());
var row_filter_source = Observable.Create<Func<Row, bool>>(o => {
notify();
_filter_changed += notify;
return Disposable.Create(() => _filter_changed -= notify);
void notify() =>
o.OnNext(get_filter_states().build_predicate());
});
foreach (var f in _all_filters) {
f.filter_changed += _refresh_filter.execute;
}
_dispose = Disposable.Create(
row_filter_source.Subscribe(f => {
// the rows are polled every 100 ms, just set the filter and let the refresh take care of the rest.
_filter = f;
}),
security_mds_source.Subscribe(_on_securities),
ObservableAutoWrapper.connect_item_hooks(_rows, r => r, true),
WorkLoop.Start("MarketView", _refresh)
);
// local methods
RenderTextColumn<Row> add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, HorizontalAlignment h_alignment = HorizontalAlignment.Right) {
var col = new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
// it's necessary to have this id populated for grid settings
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
Header = header,
@@ -185,91 +76,31 @@ namespace mbts.spark {
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
font_color_override = font_color,
horizontal_alignment = h_alignment
};
columns.Add(col);
return col;
}
});
void add_hl_price_col(Columns col_key, string header, string prop, IDataGridColumnFilter filter, Color foreground, Color? background) =>
add_hl_col(
col_key,
header,
price_binding(prop),
prop,
filter,
DataGridFilterKnownTemplates.DoubleMinMax,
DefaultColumnWidths.MViewPrice,
foreground,
background,
tooltip: _order_ticket_tooltip(col_key));
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
void add_hl_merged_size_col(Columns col_key, string sort_prop, string text_prop, IDataGridColumnFilter filter, Color foreground) =>
add_hl_col(
col_key,
merged_size_col_header,
new Binding(text_prop),
sort_prop,
filter,
DataGridFilterKnownTemplates.UIntMinMax,
DefaultColumnWidths.ImpliedDirectMergedSize,
foreground,
tooltip: _order_ticket_tooltip(col_key));
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
HyperlinkElementColumn<Columns> add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, IDataGridColumnFilter filter, Color foreground) =>
add_hl_col(
col_key,
header,
new Binding(text_prop),
sort_prop,
filter,
DataGridFilterKnownTemplates.IntMinMax,
working_orders_col_width,
foreground,
tooltip: $"Show {_orderbook_title(col_key).desc}");
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
HyperlinkElementColumn<Columns> add_hl_col(
Columns col_key,
string header,
BindingBase binding,
string prop,
IDataGridColumnFilter filter,
string filter_template,
double width,
Color? foreground = null,
Color? background = null,
HorizontalAlignment h_alignment = HorizontalAlignment.Right,
string tooltip = null,
BindingBase tooltip_binding = null) {
var col = new HyperlinkElementColumn<Columns>(
col_key, filter, filter_template) {
// it's necessary to have this id populated for grid settings
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
Header = header,
sort_member_path = prop,
font_color_override = foreground,
background_color_override = background,
TextBinding = binding,
Width = DefaultColumnWidths.Price,
horizontal_alignment = h_alignment
};
col.view_generated += on_view_generated;
columns.Add(col);
return col;
//
void on_view_generated(HyperlinkCell cell_view) {
cell_view.SetValue(ColumnKeyProperty, col_key);
cell_view.SetValue(ColumnProperty, col);
cell_view.SetBinding(GridProperty, new Binding { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DIYGrid), 1) });
if (tooltip != null) {
cell_view.ToolTip = tooltip;
} else if (tooltip_binding != null) {
cell_view.SetBinding(FrameworkElement.ToolTipProperty, tooltip_binding);
}
if (use_mouse_state_transitions)
init_hyperlink_hover_animation(cell_view, col);
}
}
void add_hl_col(
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new HyperlinkElementColumn<Row, Columns>(
col_key, binding, new Binding(command_prop), filter, filter_template) {
// it's necessary to have this id populated for grid settings
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
Header = header,
sort_member_path = prop,
font_color_override = foreground,
background_color_override = background,
Width = DefaultColumnWidths.Price,
horizontal_alignment = h_alignment
});
BindingBase price_binding(string prop) {
var mb = new MultiBinding { Converter = price_converter };
@@ -277,95 +108,72 @@ namespace mbts.spark {
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
return mb;
}
void init_hyperlink_hover_animation(HyperlinkCell cell_view, ColumnBase col) {
// background timeline
var hover_bg_timeline = VisualStateHelper.create_color_animation(
cell_view,
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
new Binding(nameof(ColumnBase.background_color)) {
Source = col,
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x1A) }
});
// background pressed timeline
var pressed_bg_timeline = VisualStateHelper.create_color_animation(
cell_view,
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
new Binding(nameof(ColumnBase.background_color)) {
Source = col,
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x25) }
});
// border timeline
var bd_brush = new SolidColorBrush();
BindingOperations.SetBinding(bd_brush, SolidColorBrush.ColorProperty, new Binding(nameof(ColumnBase.background_color)) { Source = col });
cell_view.BorderBrush = bd_brush;
cell_view.BorderThickness = new Thickness(1);
var hover_bd_timeline = VisualStateHelper.create_color_animation(
cell_view, new PropertyPath("(0).(1)", HyperlinkCell.BorderBrushProperty, SolidColorBrush.ColorProperty), Colors.Black.with(0x60));
// mouse states
VisualStateManager.GetVisualStateGroups(cell_view).Add(
VisualStateHelper.create_group("MouseStates",
new VisualState { Name = "Normal" },
VisualStateHelper.create_state("MouseOver", hover_bg_timeline, hover_bd_timeline),
VisualStateHelper.create_state("Pressed", pressed_bg_timeline, hover_bd_timeline)));
// state groups manager
StateManager.SetGroups(cell_view, new StateGroupsManager {
StateDefinitions = new StateDefinitionCollection {
new MouseStateDefinition { MouseSource = cell_view }
//
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
//
_resources.AddRange(
state.OnDispatcher().Subscribe(s => {
using (rows_view.DeferRefresh()) {
if (s.grid != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
});
}
if (s.filters is FilterStates filters) {
security_filter.load_state(filters.security);
bid_price_filter.load_state(filters.bid_price);
bid_merged_size_filter.load_state(filters.bid_merged_size);
bid_direct_size_filter.load_state(filters.bid_direct_size);
bid_implied_size_filter.load_state(filters.bid_implied_size);
ask_price_filter.load_state(filters.ask_price);
ask_merged_size_filter.load_state(filters.ask_merged_size);
ask_direct_size_filter.load_state(filters.ask_direct_size);
ask_implied_size_filter.load_state(filters.ask_implied_size);
last_price_filter.load_state(filters.last_price);
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
last_size_filter.load_state(filters.last_size);
day_change_filter.load_state(filters.day_change);
settlement_filter.load_state(filters.settlement);
session_low_filter.load_state(filters.session_low);
session_high_filter.load_state(filters.session_high);
position_filter.load_state(filters.position);
raw_day_position_filter.load_state(filters.raw_day_position);
working_bid_volume_filter.load_state(filters.working_bid_volume);
working_ask_volume_filter.load_state(filters.working_ask_volume);
volume_filter.load_state(filters.volume);
security_key_filter.load_state(filters.security_key);
}
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
}
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
}
public event PropertyChange<Row> current_row_changed { add => _current_row.Changed += value; remove => _current_row.Changed -= value; }
event Action _filter_changed;
public ICollectionView rows_view => _rows_view;
public ObservableCollection<Row> rows => _rows;
public ObservableCollection<Row> selected_rows { get; } = new ObservableCollection<Row>();
public IReadOnlyObservableCollection<SecurityImmutable> selected_securities { get; }
public IReadOnlyObservableCollection<SecurityMD> selected_security_mds { get; }
public Color selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public Row current_row { get => _current_row.Value; set => _current_row.set(value); }
public SecurityImmutable? current_security => current_row?.security;
public SecurityMD? current_security_md => current_row?.security_md;
internal IBus bus => _bus;
internal OrdersModel orders_model => _orders_model;
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> day_position_filter { get; } = new IntMinMax<Row>(r => unchecked((int)r.day_position), nameof(Row.day_position));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
IEnumerable<IDataGridColumnFilter<Row>> _all_filters =>
_filter_getters.Select(g => g(this));
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
public static IComparer<Row> get_column_comparer(Columns column) {
switch (column) {
@@ -373,9 +181,8 @@ namespace mbts.spark {
case Columns.SecurityKey: return wrap(r => r.security.Id);
case Columns.Volume: return wrap(r => r.volume);
case Columns.Last: return wrap(r => r.last_price);
case Columns.LastSpd: return wrap(r => r.last_price_from_spread);
case Columns.LastSize: return wrap(r => r.last_size);
case Columns.Position: return wrap(r => r.day_position);
case Columns.Position: return wrap(r => r.position);
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
@@ -402,60 +209,37 @@ namespace mbts.spark {
Comparer<T>.Default.wrap(selector);
}
public IComparer<Row> comparer {
get => _comparer;
set => _rows_view.CustomSort = (_comparer = value)?.to_non_generic();
}
public (FillsVM vm, string context, string title, Color fg) fills_params(IEnumerable<SecurityImmutable> securities, Columns column) {
var (title_desc, fg) = _fills_title(column);
var state = StartupData.DefaultAuxiliaryFills;
if (state.filters == null)
state.filters = new bts.shell.fills.FillsFilterStates();
switch (column) {
case Columns.RawDayPosition:
state.filters.fill_kind = new[] {
Fill.Types.FillKind.Standard
};
break;
case Columns.Position:
state.filters.fill_kind = new[] {
Fill.Types.FillKind.Standard,
Fill.Types.FillKind.Sod
};
break;
}
state.grid = GridSettingsHelper.hide_cols(state.grid, new[] { ui.spark.FillsView.Columns.Security });
var sec_keys = securities.Select(SecurityDataHelper.key_fn);
if (securities.All(s => s.ContractCase == Security.ContractOneofCase.Synthetic)) {
state.root_securities = sec_keys.ToArray();
} else {
state.filters.security = SecurityCriteria.from_keys(sec_keys);
}
var vm = new FillsVM(
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
return (vm, $"{string.Join(_security_delimiter, securities)}", title_desc, fg);
}
public (OrderBookVM vm, string context, string title, Color fg) orderbook_params(IEnumerable<SecurityImmutable> securities, Columns column) {
var (title_desc, fg) = _orderbook_title(column);
var filters = new bts.shell.orderbook.FilterStates {
security_criteria = SecurityCriteria.from_keys(securities.Select(SecurityDataHelper.key_fn)),
order_types = securities.All(s => s.ContractCase == Security.ContractOneofCase.Synthetic) ?
new[] { new AlgoID(CustomOrderDefinition.CustomOneofCase.Spreader) } :
AlgoID.exchange_algo_keys,
statuses = new[] { OrderStatus.Working }
};
switch (column) {
case Columns.WorkingBids: filters.sides = new[] { Side.Buy }; break;
case Columns.WorkingAsks: filters.sides = new[] { Side.Sell }; break;
}
var vm = _mk_orderbook_vm_common(filters, new[] {
OrderBookGridView.ColumnKeys.Security,
OrderBookGridView.ColumnKeys.Side
});
return (vm, $"{string.Join(_security_delimiter, securities)}", title_desc, fg);
}
#region Filters
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
public FilterStates get_filter_states() {
return new FilterStates {
@@ -475,7 +259,7 @@ namespace mbts.spark {
settlement = settlement_filter.get_state(),
session_low = session_low_filter.get_state(),
session_high = session_high_filter.get_state(),
day_position = day_position_filter.get_state(),
position = position_filter.get_state(),
raw_day_position = raw_day_position_filter.get_state(),
working_bid_volume = working_bid_volume_filter.get_state(),
working_ask_volume = working_ask_volume_filter.get_state(),
@@ -489,109 +273,14 @@ namespace mbts.spark {
mic_code = mic_code_filter.get_state(),
};
}
#endregion
public void load_filters(FilterStates filters) {
if (filters != null) {
using (rows_view.DeferRefresh()) {
security_filter.load_state(filters.security);
bid_price_filter.load_state(filters.bid_price);
bid_merged_size_filter.load_state(filters.bid_merged_size);
bid_direct_size_filter.load_state(filters.bid_direct_size);
bid_implied_size_filter.load_state(filters.bid_implied_size);
ask_price_filter.load_state(filters.ask_price);
ask_merged_size_filter.load_state(filters.ask_merged_size);
ask_direct_size_filter.load_state(filters.ask_direct_size);
ask_implied_size_filter.load_state(filters.ask_implied_size);
last_price_filter.load_state(filters.last_price);
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
last_size_filter.load_state(filters.last_size);
day_change_filter.load_state(filters.day_change);
settlement_filter.load_state(filters.settlement);
session_low_filter.load_state(filters.session_low);
session_high_filter.load_state(filters.session_high);
day_position_filter.load_state(filters.day_position);
raw_day_position_filter.load_state(filters.raw_day_position);
working_bid_volume_filter.load_state(filters.working_bid_volume);
working_ask_volume_filter.load_state(filters.working_ask_volume);
volume_filter.load_state(filters.volume);
security_key_filter.load_state(filters.security_key);
}
}
}
static (string desc, Color fg) _fills_title(Columns key) {
switch (key) {
case Columns.Position: return ("Day Fills", Colors.Black);
case Columns.RawDayPosition: return ("Raw Day Fills", Colors.Black);
default: return ("Fills", Colors.Black);
}
}
static (string desc, Color fg) _orderbook_title(Columns key) {
switch (key) {
case Columns.WorkingBids: return ("Working Buy Orders", BTSColors.MarketData.bid);
case Columns.WorkingAsks: return ("Working Sell Orders", BTSColors.MarketData.ask);
default: return ("Working Orders", Colors.Black);
}
}
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
grid.with(_hide_cols(grid.columns, columns));
static IEnumerable<GridColumnSettings> _hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<OrderBookGridView.ColumnKeys> columns) {
// hide columns who's data is implied by the grouping
var col_hash = new HashSet<string>(columns.Select(k => k.ToString()));
foreach (var c in cols)
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
}
static string _order_ticket_tooltip(Columns key) {
var bid_or_ask = Regex.Match(key.ToString(), "(Bid|Ask)");
return bid_or_ask.Success ? $"Order Ticket @ {bid_or_ask.Value}" : "Order Ticket";
}
OrderBookVM _mk_orderbook_vm_common(bts.shell.orderbook.FilterStates filters, IEnumerable<OrderBookGridView.ColumnKeys> hide_cols, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
var state = new bts.shell.orderbook.OrderBookStartupData(filters, bts.shell.orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_cols);
var settings_man = new WindowSettingsManager<bts.shell.orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = orders_source == null ?
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
new OrderBookVM(_bus, null, _orders_model, orders_source, settings_man, true);
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
obvm.columns.Insert(0, c);
obvm.load_from(state);
return obvm;
}
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
protected override void on_row_removed(Row row) => row.Dispose();
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
void _refresh() {
foreach (var r in _rows) {
r.refresh();
r.is_visible = _filter == null || _filter(r);
}
}
Row _row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
selected_rows.RemoveSorted(e.RemovedItems.OfType<Row>(), _row_key_comparer);
selected_rows.InsertSorted(e.AddedItems.OfType<Row>(), _row_key_comparer);
}
void _on_securities(TableUpdate<SecurityMD, uint> table) {
var sw = new Stopwatch();
sw.Start();
foreach (var smd in table.removed) {
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
Log.assert(i >= 0);
_wrapped_rows.RemoveAt(i);
}
foreach (var smd in table.added) {
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
Log.assert(i < 0);
_wrapped_rows.Insert(~i, smd);
}
sw.Stop();
Log.Ui.info($"MarketView update took {sw.ElapsedMilliseconds:0.00}ms.");
foreach (var r in _row_vms) r.refresh();
}
}
}
@@ -3,12 +3,8 @@
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bv="clr-namespace:bts.view"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
u:WindowStyle.UnsafeWindow="{Binding Path='(u:WindowStyle.UnsafeWindow)', RelativeSource={RelativeSource AncestorType=bv:SparkWindowView}}"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -17,56 +13,8 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="c:HyperlinkCell">
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_brush', RelativeSource={RelativeSource Self}}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Command" Value="{x:Static usp:MarketViewView.CellClickCommand}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static usp:MarketViewView.CellConverter}">
<Binding Path=""/>
<Binding Path="(c:DIYGrid.ColumnKey)" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=usp:MarketViewView}}"/>
<Style.Triggers>
<Trigger Property="IsMouseOverClickArea" Value="True">
<Setter Property="Background">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:ColorBlendMultiConverter/>
<cv:ColorToBrushConverter/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="(c:DIYGrid.Grid).SelectionHighlight" RelativeSource="{RelativeSource Self}"/>
<Binding Path="(c:DIYGrid.Column).background_color" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<Binding Path="SelectionHighlight" RelativeSource="{RelativeSource AncestorType=c:DIYGrid}">
<Binding.Converter>
<cv:ValueConverterPipe>
<cv:ColorWithConverter A="255"/>
<cv:ColorToBrushConverter/>
</cv:ValueConverterPipe>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static usp:MarketViewView.CellClickCommand}" Executed="{x:Static usp:MarketViewView.CellClickExecuted}"/>
</UserControl.CommandBindings>
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
@@ -74,14 +22,13 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:DIYGrid
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
CurrentRow="{Binding current_row}"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding selection_highlight, Mode=OneWay}"
SyncColumnSortDirectionWithSortDescriptions="False"
>
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
CurrentRow="{Binding current_row}"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
@@ -1,37 +1,14 @@
using bts.api;
using mbts.spark;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.extensions;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for MarketSheetsView.xaml
/// </summary>
partial class MarketViewView : UserControl {
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
MarketViewVM _vm;
DIYGridPopupManager _popup_man_backing;
public MarketViewView() {
InitializeComponent();
this.ConnectDataContext<MarketViewVM>(vm => _vm = vm);
AdornerHelper.attach_continuously_on_loaded(
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
_grid);
}
public enum Columns {
None,
Security,
Volume,
Last,
@@ -62,116 +39,14 @@ namespace mbts.ui.spark {
MicCode
}
public static ExecutedRoutedEventHandler
CellClickExecuted => CommandImpl.cell_click;
public static IMultiValueConverter CellConverter =>
ValueConverter.create<MarketViewVM.Row, Columns, MarketViewCell>((r, c) => new MarketViewCell(r, c));
IBus _bus => _vm.bus;
OrdersModel _model => _vm.orders_model;
DIYGridPopupManager _popup_man =>
_popup_man_backing ?? (_popup_man_backing = DIYGridPopupManager.create(this));
public void show_working_orders(IReadOnlyList<SecurityImmutable> securities) =>
_show_mini_orderbook(securities, Columns.None);
public void show_mini_fills(IReadOnlyList<SecurityImmutable> securities) =>
_show_mini_fills(securities, Columns.None);
public void show_order_ticket(SecurityMD security_md) =>
_show_order_ticket(security_md);
void _on_cell_click(MarketViewVM.Row row, Columns col) {
switch (col) {
case Columns.WorkingAsks:
case Columns.WorkingBids:
_show_mini_orderbook(row.security, col);
break;
case Columns.Bid:
_show_order_ticket(row.security_md, row.bid_price);
break;
case Columns.BidMergedSize: _show_order_ticket(row.security_md, row.bid_price, row.bid_merged_size); break;
case Columns.BidDirectSize: _show_order_ticket(row.security_md, row.bid_price, row.bid_direct_size); break;
case Columns.BidImpliedSize: _show_order_ticket(row.security_md, row.bid_price, row.bid_implied_size); break;
case Columns.Ask:
_show_order_ticket(row.security_md, row.ask_price);
break;
case Columns.AskMergedSize: _show_order_ticket(row.security_md, row.ask_price, row.ask_merged_size); break;
case Columns.AskDirectSize: _show_order_ticket(row.security_md, row.ask_price, row.ask_direct_size); break;
case Columns.AskImpliedSize: _show_order_ticket(row.security_md, row.ask_price, row.ask_implied_size); break;
case Columns.Last:
_show_order_ticket(row.security_md, row.last_price);
break;
case Columns.LastSize:
_show_order_ticket(row.security_md, row.last_price, row.last_size);
break;
case Columns.Position:
case Columns.RawDayPosition:
_show_mini_fills(row.security, col);
break;
}
}
void _show_mini_orderbook(SecurityImmutable security, Columns col) =>
_show_mini_orderbook(new[] { security }, col);
void _show_mini_orderbook(IReadOnlyList<SecurityImmutable> securities, Columns col) {
var (vm, context, title, fg) = _vm.orderbook_params(securities, col);
_show_popup(vm, new AuxiliaryDataHeader {
context = context,
title = title,
title_foreground = fg
});
}
void _show_mini_fills(SecurityImmutable security, Columns col) =>
_show_mini_fills(new[] { security }, col);
void _show_mini_fills(IEnumerable<SecurityImmutable> securities, Columns col) {
var (vm, context, title, fg) = _vm.fills_params(securities, col);
_show_popup(vm, new AuxiliaryDataHeader {
context = context,
title = title,
title_foreground = fg
});
}
void _show_order_ticket(SecurityMD security_md, double price = double.NaN, uint size = 0) {
var resources = new OrderTicketResources();
resources.MergedDictionaries.Add(new AuxiliaryDataResources());
var ticket_vm = new NewOrderTicketVM(
_model, security_md, _model.factory.order(security_md.security), price, size, Observable.Return(mbts.spark.LadderSettings.Default.calculator_buttons));
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
}
void _show_popup(object content, object header) {
var resources = new AuxiliaryDataResources();
resources.MergedDictionaries.Add(new AutoHideScrollbarResources());
_show_popup(content, header, resources);
}
void _show_popup(object content, object header, ResourceDictionary resources) {
var view = new HeaderedContentControl {
DataContext = content,
Content = content,
Header = header,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Resources = resources ?? new AuxiliaryDataResources()
};
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
_popup_man.show(view);
}
static class CommandImpl {
public static void cell_click(object sender, ExecutedRoutedEventArgs e) {
if (sender is MarketViewView mview && e.Parameter is MarketViewCell cell)
mview._on_cell_click(cell.row, cell.column);
e.Handled = true;
}
public MarketViewView() {
InitializeComponent();
AdornerHelper.attach_continuously_on_loaded(
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
_grid);
}
}
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
}
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
}
@@ -1,21 +1,18 @@
using bts;
using bts.api;
using bts.api.core;
using bts.collections;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -24,18 +21,9 @@ using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
{ MarketViewView.Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
{ MarketViewView.Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
{ MarketViewView.Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ MarketViewView.Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
List<SortingCriteria> _current_sorting_criterion;
MarketViewVM _vm;
MarketViewView _view;
MarketViewWindowFactory.StartupData _state;
@@ -44,7 +32,6 @@ namespace mbts.spark {
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
SimpleGridSettingsManager<MarketViewVM.Row> _grid_settings_manager;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
@@ -55,17 +42,13 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
}
event Action<SecurityCriteria> _security_criteria_changed;
event Action<MarketViewWindowFactory.StartupData> _state_changed;
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(
_state.security_criteria,
_vm.get_filter_states(),
_settings_manager.instance_settings,
_current_sorting_criterion);
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -85,47 +68,43 @@ namespace mbts.spark {
return _selected_security_criteria_backing;
}
}
SecurityCriteria _sorted_selected_security_criteria => _sorted_selected_security_criteria_backing ?? (
_sorted_selected_security_criteria_backing = _vm != null ?
new SecurityCriteria(_vm.selected_rows.Ordered(_vm.comparer).Select(r => r.security)) :
null);
SecurityCriteria _sorted_selected_security_criteria {
get {
if (_sorted_selected_security_criteria_backing == null) {
if (_vm != null) {
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
foreach (var sc in sorting_criteria) {
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
} else {
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
}
}
}
var comp = comp_builder.to_comparer();
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
}
}
return _sorted_selected_security_criteria_backing;
}
}
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
var security_md_map_src = Observable.Create<IReadOnlyDictionary<uint, SecurityMD>>(o => {
IDisposable sub = null;
_security_criteria_changed += on_security_criteria_changed;
on_security_criteria_changed(_state.security_criteria);
return Disposable.Create(dispose);
void on_security_criteria_changed(SecurityCriteria security_criteria) {
sub?.Dispose();
sub = _state.security_criteria.subscribe_md_map(_bus).Subscribe(o.OnNext);
}
void dispose() =>
Disposable.dispose(ref sub);
});
_vm = new MarketViewVM(
_bus,
_orders_model,
security_md_map_src
.BufferLatest(TimeSpan.FromSeconds(1))
.Table(SecurityMD.key_fn)
.Where(t => t.has_changes())
.OnDispatcher());
var sort_man = new ColumnSortManager(_vm.columns, MarketViewView.Columns.Security.ToString());
_current_sorting_criterion = _state.sorting_criterion.ToList();
sort_man.load(_state.sorting_criterion);
_vm.load_filters(_state.filters);
_vm.comparer = sort_man.build_comparer<MarketViewView.Columns, MarketViewVM.Row>(MarketViewVM.get_column_comparer);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
_view._grid.Sorting += on_grid_sorting;
_grid_settings_manager = new SimpleGridSettingsManager<MarketViewVM.Row>(_vm.columns, _col_settings_flags, _vm.rows);
_grid_settings_manager.load_from(_settings_manager.current_settings());
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
@@ -147,13 +126,6 @@ namespace mbts.spark {
});
var show_fills_actual_cmd = SparkCommands.ShowFills();
var show_fills_cmd_wrapper = new SparkCommandCommand(show_fills_actual_cmd.title, show_fills_actual_cmd.icon, show_fills_cmd, SparkCommands.KeyGestures.ShowFills);
var show_mini_fills_cmd = new DelegateCommand(() => _view.show_mini_fills(_vm.selected_securities), () => _vm.selected_securities.Count > 0);
var show_ticket_cmd = new DelegateCommand(() => {
var smd = _vm.current_security_md;
if (smd.HasValue)
_view.show_order_ticket(smd.Value);
}, () => _vm.current_security_md.HasValue);
var show_ticket_actual_cmd = SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket);
// show synthetic create
var show_synth_create_cmd = new DelegateCommand(() => {
var sc = _sorted_selected_security_criteria;
@@ -162,29 +134,15 @@ namespace mbts.spark {
}, () => _vm.selected_rows.Count > 0 && _vm.selected_rows.Count <= 24);
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
var show_working_orders_cmd = new DelegateCommand(() => _view.show_working_orders(_vm.selected_securities), () => _vm.selected_securities.Count > 0);
var current_sec_md_src = Observable.Create<SecurityMD?>(o => {
notify(null, null);
_vm.current_row_changed += notify;
return Disposable.Create(() => _vm.current_row_changed -= notify);
void notify(MarketViewVM.Row oldval, MarketViewVM.Row newval) {
if (newval != null)
o.OnNext(newval.security_md);
else o.OnNext(null);
}
});
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
// commands
_commands = new List<SparkCommand> {
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_rows.Observe().Select(_ => _vm.selected_rows.WrapSelectorRO(r => r.security_md)), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_sec_md_src, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
show_fills_cmd_wrapper,
show_tas_cmd_wrapper,
show_synth_create_cmd_wrapper,
// Note: the commands commented out below open the mini popup versions of their respective windows
// TODO: determine if/how to integrate the mini popups into the toolbar.
//new SparkCommandCommand("Working Orders", Icons.Orders, show_working_orders_cmd),
//new SparkCommandCommand("Fills", show_fills_actual_cmd.icon, show_mini_fills_cmd),
//new SparkCommandCommand(show_ticket_actual_cmd.title, show_ticket_actual_cmd.icon, show_ticket_cmd),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
@@ -192,7 +150,7 @@ namespace mbts.spark {
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
s => _grid_settings_manager.create_vm(
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
@@ -205,40 +163,26 @@ namespace mbts.spark {
}))
};
//
_vm.selected_rows.CollectionChanged += on_selection_changed;
_subscriptions = new CompositeDisposable(
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c => {
_state = new MarketViewWindowFactory.StartupData(c, _state.filters, _state.grid, _state.sorting_criterion.ToList());
_security_criteria_changed?.Invoke(c);
}),
_settings_manager.subscribe_current().OnDispatcher().Subscribe(new CompositeObserver<GridSettings>(_grid_settings_manager) {
s => _vm.selection_highlight = s.selection_highlight
})) {
_vm.selection_changed += on_selection_changed;
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
() => {
_vm.selected_rows.CollectionChanged -= on_selection_changed;
_vm.selection_changed -= on_selection_changed;
_view._grid.Sorting -= on_grid_sorting;
}
};
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
// invalidate selected security criteria
_selected_security_criteria_backing = null;
_sorted_selected_security_criteria_backing = null;
show_tas_cmd.RaiseCanExecuteChanged();
show_fills_cmd.RaiseCanExecuteChanged();
show_synth_create_cmd.RaiseCanExecuteChanged();
show_working_orders_cmd.RaiseCanExecuteChanged();
show_mini_fills_cmd.RaiseCanExecuteChanged();
show_ticket_cmd.RaiseCanExecuteChanged();
}
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) {
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) =>
_sorted_selected_security_criteria_backing = null;
sort_man.on_column_click(e.Column, KeyboardExtensions.IsShiftDown());
_vm.comparer = sort_man.build_comparer<MarketViewView.Columns, MarketViewVM.Row>(MarketViewVM.get_column_comparer);
_current_sorting_criterion = sort_man.create().ToList();
e.Handled = true;
}
}
public override void Dispose() {
@@ -1,7 +1,6 @@
using bts.api;
using bts.proto;
using bts.settings;
using bts.utils;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -89,22 +88,7 @@ namespace bts.shell {
}
public static GridSettings DefaultGridSettings =>
GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
public static fills.FillsSettings DefaultAuxiliaryFills {
get {
var grid = fills.FillsSettings.MiniGridSettings;
//if (grid.columns.TryGetFirst(c => c.id == nameof(mbts.ui.spark.FillsView.Columns.Side), out var side_col, out int side_col_pos)) {
// var cols = grid.columns.ToList();
// cols[side_col_pos] = side_col.with(is_visible: false);
// grid = grid.with(cols);
//}
var rv = fills.FillsSettings.Default();
rv.grid = grid;
return rv;
}
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
@@ -212,7 +196,7 @@ namespace bts.shell {
[ProtoMember(11)]
public DoubleMinMaxState session_high;
[ProtoMember(12)]
public IntMinMaxState day_position;
public IntMinMaxState position;
[ProtoMember(27)]
public DoubleMinMaxState raw_day_position;
[ProtoMember(13)]
@@ -245,47 +229,6 @@ namespace bts.shell {
public string last_price_from_spread;
[ProtoMember(28)]
public string mic_code;
public Func<MarketViewVM.Row, bool> build_predicate() {
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
// security
pb.add_regex(security, r => r.security.ToString());
pb.add_regex(security_key, r => r.security_key.ToString());
// bid
pb.add_numeric(bid_price, r => r.bid_price);
pb.add_numeric(bid_merged_size, r => r.bid_merged_size);
pb.add_numeric(bid_direct_size, r => r.bid_direct_size);
pb.add_numeric(bid_implied_size, r => r.bid_implied_size);
pb.add_numeric(working_bid_volume, r => r.working_bid_volume);
// ask
pb.add_numeric(ask_price, r => r.ask_price);
pb.add_numeric(ask_implied_size, r => r.ask_implied_size);
pb.add_numeric(ask_direct_size, r => r.ask_direct_size);
pb.add_numeric(ask_merged_size, r => r.ask_merged_size);
pb.add_numeric(working_ask_volume, r => r.working_ask_volume);
// last
pb.add_numeric(last_price, r => r.last_price);
pb.add_regex(last_price_from_spread, r => r.last_price_from_spread.ToString());
pb.add_numeric(last_size, r => r.last_size);
// position
// TODO: convert day_position to DoubleMinMaxState
pb.add_numeric(day_position, r => unchecked((int)r.day_position));
pb.add_numeric(raw_day_position, r => r.raw_day_position);
// market
pb.add_numeric(day_change, r => r.day_change);
pb.add_numeric(settlement, r => r.settlement);
pb.add_numeric(session_low, r => r.session_low);
pb.add_numeric(session_high, r => r.session_high);
pb.add_numeric(volume, r => r.volume);
// ice
pb.add_regex(market_description, r => r.market_description);
pb.add_regex(cleared_alias, r => r.cleared_alias);
pb.add_regex(product_description, r => r.product_description);
pb.add_regex(hub_alias, r => r.hub_alias);
pb.add_regex(strip_name, r => r.strip_name);
pb.add_regex(mic_code, r => r.mic_code);
return pb.to_predicate();
}
}
// migration rules
@@ -328,11 +271,5 @@ namespace bts.shell {
return false;
}
}
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class EnsureSelectionHighlightTranslucency : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
}
}
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.co2;
using bts.collections;
using bts.utils.csv;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
@@ -52,7 +53,7 @@ namespace mbts.spark {
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
@@ -173,6 +174,19 @@ namespace mbts.spark {
}
}
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesVM.Row),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public void raise_invalidated() => invalidated?.Invoke();
}
}
@@ -13,5 +13,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row> { }
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
}
@@ -3,6 +3,7 @@ using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils.csv;
using mbts.co2;
using mbts.persist;
using mbts.ui;
@@ -63,6 +64,7 @@ namespace mbts.spark {
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
_commands = new SparkCommand[] {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.value.row_vms.ToCsv(TimeAndSalesVM.Row.csv_ordered_properties), "TaS")),
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
}
@@ -47,13 +47,13 @@ namespace mbts.spark {
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
columns.Add(new HyperlinkElementColumn<Columns>(
columns.Add(new HyperlinkElementColumn<Row, Columns>(
Columns.n_working_orders,
new Binding(nameof(Row.n_working_orders)),
new Binding(nameof(Row.show_order_book)),
n_working_orders_filter,
DataGridFilterKnownTemplates.UIntMinMax) {
id = col_id(Columns.n_working_orders),
CommandBinding = new Binding(nameof(Row.show_order_book)),
TextBinding = new Binding(nameof(Row.n_working_orders)),
Header = DefaultColumnHeaders.N_Working_Orders,
Width = DefaultColumnWidths.N_Working_Orders
});
@@ -5,6 +5,7 @@ using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.collections;
using mbts.persist;
using mbts.spark.filters;
@@ -23,6 +24,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using static bts.api.orderman.Fill.Types;
using static bts.shell.FillsWindowFactory;
@@ -214,13 +216,10 @@ namespace mbts.spark {
((IKeyedColumn<Columns>)column).key;
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
var orders_hash = state.orders.is_null_or_empty() ? null : new HashSet<uint>(state.orders);
var root_securities_hash = state.root_securities.is_null_or_empty() ? null : new HashSet<uint>(state.root_securities);
return orders_hash == null && root_securities_hash == null ?
model.on_fill_tuples_with_root :
model.on_fill_tuples_with_root.Filter(f =>
(orders_hash == null || orders_hash.Contains(f.fill.OrderKey)) &&
(root_securities_hash == null || root_securities_hash.Contains(f.root_order_security?.security?.Id ?? 0)));
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples_with_root.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples_with_root;
}
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
@@ -301,6 +300,7 @@ namespace mbts.spark {
public FillStatus fill_status => _fill.FillStatus;
public string fill_key => _fill.FillKey;
public string order_key => _order.Key.ToString();
public string symbol => fill_tuple.security?.Symbol;
// Root Algo
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
@@ -312,6 +312,28 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(symbol),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
nameof(fill_time),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(size),
nameof(price),
nameof(fill_kind),
nameof(fill_status),
nameof(fill_key),
nameof(order_key),
nameof(security_key)
});
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
}
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
}
@@ -3,6 +3,7 @@ using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -59,6 +60,7 @@ namespace mbts.spark {
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
@@ -72,11 +72,5 @@ namespace bts.shell {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(FillsSettings.DefaultGridSettings.columns, out new_settings);
}
[Export(typeof(IFillsGridSettingsMigrationRule))]
class EnsureSelectionHighlightTranslucency : IFillsGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
}
}
}
@@ -38,8 +38,6 @@ namespace bts.shell.fills {
public static FillsSettings Default() =>
new FillsSettings(null, null, new FillsFilterStates(), null);
public uint[] root_securities { get; set; }
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
@@ -467,6 +467,10 @@ namespace mbts.spark {
settings_invalidated?.Invoke();
}
public void recenter_all_ladders() {
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
public Task send_order_async((double price, Side? side, MouseButton button) args) {
if (order_properties_vm != null && args.button != MouseButton.Middle) {
var size_from_alternate = args.button == MouseButton.Right;
@@ -12,6 +12,7 @@ using mbts.ui.spark.algo;
using mbts.ui.utility.text;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -255,8 +256,9 @@ namespace mbts.spark {
DependencyProperty.Register("Orders", typeof(ObservableCollection<LadderOrderVM>), typeof(LadderGridHost), new PropertyMetadata(null, orders_changed));
static void orders_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is LadderGridHost lgh)
WPFHelper.manage_collection_change(e, lgh.orders_collection_changed);
LadderGridHost lgh = d as LadderGridHost;
WPFHelper.manage_collection_change(e, lgh.orders_collection_changed, src => lgh._ordershost.clear_orders(),
src => lgh.orders_collection_changed(lgh, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)src)));
}
void orders_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
@@ -488,8 +490,14 @@ namespace mbts.spark {
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
var column = _column_for_point(e.GetPosition(this));
if (column.column_type == ColumnType.Price)
_scroll_to_center();
if (column.column_type == ColumnType.Price) {
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
_vm?.recenter_all_ladders();
} else {
_scroll_to_center();
}
}
}
#region Row Utility Methods
@@ -22,7 +22,7 @@ namespace mbts.spark {
DataContext = vm;
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public MiniOrderBookRowView() : base() {
FocusManager.SetIsFocusScope(this, true);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
@@ -650,13 +650,12 @@ namespace mbts.spark {
ListSortDirection? _cached_sort_direction;
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
: base(Columns.LastMessage, filter, DataGridFilterKnownTemplates.Regex) {
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DefaultColumnWidths.LastMessage;
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
@@ -4,6 +4,7 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.ui;
using mbts.ui.controls;
using System;
@@ -11,6 +12,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
@@ -60,6 +62,35 @@ namespace mbts.spark {
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(OrderBookRowVM),
new string[] {
nameof(security_text),
nameof(symbol),
nameof(time_created),
nameof(last_change),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(order_type),
nameof(root_algo_display_name),
nameof(order_sub_type),
nameof(side),
nameof(quantity),
nameof(filled_quantity),
nameof(price),
nameof(avg_price),
nameof(last_price),
nameof(piq),
nameof(status),
// last message (could contains commas, so skipping)
nameof(security_key),
nameof(order_key),
nameof(parent_order_key),
});
// Core
public OrderSecurityRootPair order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order.order;
@@ -4,6 +4,7 @@ using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils.csv;
using bts.view;
using mbts.persist;
using mbts.ui;
@@ -184,6 +185,7 @@ namespace mbts.spark {
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
toggle_show_right_panel_command,
attach_selected_orders_command,
detach_selected_orders_command,
@@ -9,6 +9,7 @@
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
InactiveSelectionOpacity="0.3"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
@@ -99,7 +99,7 @@ namespace mbts.spark {
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public OrderBookRowView() : base() { }
}
}
@@ -1,220 +1,223 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
for (int i = 0; i < s.grid.columns.Count; i++) {
var key = s.grid.columns[i].strong_id<ColumnKeys>();
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
// anchor index is 1 based, subtract 1 to get 0 based
int anchor_index = a.anchor_index.Value - 1;
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
new_cols.Value.RemoveAt(i);
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
List<GridColumnSettings> new_cols = null;
IEnumerable<GridColumnSettings> cols = s.grid.columns;
var sorted_anchors = anchor_items.OrderBy(a => a.anchor_index).ToList();
for (int i = 0; i < sorted_anchors.Count; i++) {
if (GridSettingsHelper.try_find_column(cols, sorted_anchors[i].key, out var match, out int pos) && pos != i) {
if (new_cols == null) {
new_cols = s.grid.columns.ToList();
// set cols to new_cols so subsequent position lookups
// take previous column order manipulations into account.
cols = new_cols;
}
new_cols.RemoveAt(pos);
new_cols.Insert(i, match);
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols == null ? null : s.grid.with(columns: new_cols),
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
@@ -90,12 +90,11 @@ namespace bts.shell.pnl {
}
int? _try_compare_groups(CollectionViewGroup x, CollectionViewGroup y) {
if (x == null || y == null)
return null;
var xcell = (IPNLCellData)x.Name;
var ycell = (IPNLCellData)y.Name;
var comp = _get_column_comparer(xcell.column);
return comp.value_comparer.Compare(xcell.value, ycell.value);
if (x?.Name is IPNLCellData xcell && y?.Name is IPNLCellData ycell) {
var comp = _get_column_comparer(xcell.column);
return comp.value_comparer.Compare(xcell.value, ycell.value);
}
return null;
}
}
}
}
@@ -49,9 +49,8 @@ namespace bts.shell.pnl {
PNLHeaderRowDisplay _header_row_display = PNLHeaderRowDisplay.Total;
PNLRowCollectionView _rows_view;
IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> _sorting_hierarchy;
Color
_grid_background_color = Colors.Transparent,
_selection_highlight;
Brush _selection_highlight;
Color _grid_background_color = Colors.Transparent;
bool _is_paused;
// default appearances
ImmutableDictionary<ColumnKeys, PNLCellAppearance>
@@ -176,7 +175,7 @@ namespace bts.shell.pnl {
}
}
public Color selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
public Brush selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
public Color grid_background_color { get => _grid_background_color; set => Set(ref _grid_background_color, value); }
public IValueConverter group_background_converter => _group_background_converter;
@@ -292,7 +291,7 @@ namespace bts.shell.pnl {
};
public void load_from(PNLSettings settings) {
selection_highlight = settings.grid.selection_highlight;
selection_highlight = settings.grid.selection_highlight.to_brush();
grid_background_color = settings.grid.background;
calculation_type = settings.calculation_type;
if (settings.filters != null) {
@@ -382,7 +381,7 @@ namespace bts.shell.pnl {
_append_key_filters(state.filters, key);
_append_row_filters(state.filters, row);
// hide implicit cols
state.grid = GridSettingsHelper.hide_cols(state.grid, anchors);
state.grid = _hide_cols(state.grid, anchors);
return new FillsVM(
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
@@ -392,7 +391,7 @@ namespace bts.shell.pnl {
_append_key_filters(state.filters, key);
_append_group_filters(state.filters, group, anchors);
// hide implicit cols
state.grid = GridSettingsHelper.hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
return new FillsVM(
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
}
@@ -402,7 +401,7 @@ namespace bts.shell.pnl {
state.grid = orderbook.OrderBookStartupData.DefaultFillsGrid.general;
state.filters = new fills.FillsFilterStates();
// hide implicit cols
state.grid = GridSettingsHelper.hide_cols(orderbook.OrderBookStartupData.DefaultFillsGrid.general, anchors);
state.grid = _hide_cols(orderbook.OrderBookStartupData.DefaultFillsGrid.general, anchors);
_append_key_filters(state.filters, key);
var src = _orders_model.on_fill_tuples;
switch (header) {
@@ -533,6 +532,16 @@ namespace bts.shell.pnl {
new PNLColumnComparer<T>(selector);
}
static GridSettings _hide_cols(GridSettings grid, IEnumerable<ColumnKeys> columns) =>
grid.with(_hide_cols(grid.columns, columns));
static IEnumerable<GridColumnSettings> _hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<ColumnKeys> columns) {
// hide columns who's data is implied by the grouping
var col_hash = new HashSet<string>(columns.Select(k => k.ToString()));
foreach (var c in cols)
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
}
#region Fills Popup Utils
static void _append_key_filters(fills.FillsFilterStates f, ColumnKeys key) {
switch (key) {
@@ -671,7 +680,7 @@ namespace bts.shell.pnl {
OrderBookVM _mk_orderbook_vm_common(orderbook.FilterStates filters, IEnumerable<ColumnKeys> hide_counterparts, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
var state = new orderbook.OrderBookStartupData(filters, orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
state.orderbook_grid.general = GridSettingsHelper.hide_cols(state.orderbook_grid.general, hide_counterparts);
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_counterparts);
var settings_man = new WindowSettingsManager<orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = orders_source == null ?
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
@@ -12,7 +12,6 @@
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
Tag="{Binding ElementName=_grid}"
>
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static pnl:PNLView.CellClickCommand}" Executed="_cell_click_executed"/>
@@ -30,10 +29,6 @@
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
<!--Converters-->
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:ValueConverterPipe x:Key="GroupRowHeightConverter">
<cv:RangeBlockConverter Minimum="{x:Static pnl:PNLView.MinGroupRowHeight}"/>
<cv:GridLengthConverter UnitType="Pixel"/>
</cv:ValueConverterPipe>
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
<cv:PartialConverter/>
<cv:ColorToBrushConverter/>
@@ -263,7 +258,7 @@
</Setter>
</Style>
<Style x:Key="TextCellBase" TargetType="c:TextCell">
<Style x:Key="RowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
@@ -274,14 +269,10 @@
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<Style x:Key="RowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -293,7 +284,20 @@
</Setter>
</Style>
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -305,8 +309,21 @@
</Setter>
</Style>
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -318,23 +335,7 @@
</Setter>
</Style>
<Style x:Key="HyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="c:DIYGrid.Grid" Value="{Binding RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"/>
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Style x:Key="HyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
@@ -348,46 +349,41 @@
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Text" Value="{Binding text}"/>
<Style.Triggers>
<Trigger Property="IsMouseOverClickArea" Value="True">
<Setter Property="Background">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:ColorBlendMultiConverter/>
<cv:ColorToBrushConverter/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="(c:DIYGrid.Grid).SelectionHighlight" RelativeSource="{RelativeSource Self}"/>
<Binding Path="background"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<Binding Path="(c:DIYGrid.Grid).SelectionHighlight" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<cv:ValueConverterPipe>
<cv:ColorWithConverter A="255"/>
<cv:ColorToBrushConverter/>
</cv:ValueConverterPipe>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
</Style.Triggers>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
<c:CachedColumnParam SourcePropertyName="font_family" TargetProperty="c:HyperlinkCell.FontFamily"/>
<c:CachedColumnParam SourcePropertyName="font_size" TargetProperty="c:HyperlinkCell.FontSize"/>
<c:CachedColumnParam SourcePropertyName="font_brush" TargetProperty="c:HyperlinkCell.Foreground"/>
<c:CachedColumnParam SourcePropertyName="horizontal_alignment" TargetProperty="c:HyperlinkCell.HorizontalContentAlignment"/>
</x:Array>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -407,17 +403,24 @@
</Setter>
</Style>
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<!--
Note: the DIYGrid.Grid property serves as a proxy for the DIYGrid, because the header rows, unlike the regular rows/groups,
are not visual children of the DIYGrid, and cannot access the grid with a RelativeSource.
Such a proxy allows the hyperlink cell base style triggers to display a hover brush that is a blend of the grid's SelectionHighlight color and the column's background.
Caution: the fact that Element is used in this Setter.Value binding makes it precarious (if anyone renames _grid, this binding could silently break).
-->
<Setter Property="c:DIYGrid.Grid" Value="{Binding ElementName=_grid}"/>
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -451,7 +454,23 @@
</Setter>
</Style>
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -487,7 +506,7 @@
<Grid
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
c:DIYGrid.SyncWithColumnGeometry="True"
Height="{Binding RowHeight, ElementName=_grid, Converter={x:Static pnl:PNLView.HeaderRowHeightConverter}}"
Height="{Binding RowHeight, ElementName=_grid}"
>
<Grid.Visibility>
<Binding Path="header_row_display">
@@ -540,7 +559,7 @@
<Grid
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
c:DIYGrid.SyncWithColumnGeometry="True"
Height="{Binding RowHeight, ElementName=_grid, Converter={x:Static pnl:PNLView.HeaderRowHeightConverter}}"
Height="{Binding RowHeight, ElementName=_grid}"
>
<Grid.Visibility>
<Binding Path="header_row_display">
@@ -590,6 +609,7 @@
c:DIYGrid.CachedColumnParamsContainer="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"
VirtualizingPanel.ScrollUnit="Pixel"
ScrollViewer.CanContentScroll="True"
RowHeight="24"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding selection_highlight}"
SupportGroupItems="True"
@@ -678,8 +698,8 @@
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_filter.open, Source={u:DirectReference _grid}}"
key="Security"
sort_member_path="security_cell.cell_data"
data_member_path="security"
data_member_path="security_cell.cell_data"
sort_member_path="security"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
<!--Security Key-->
@@ -1033,7 +1053,7 @@
<!--Row Template-->
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}">
<Grid.Background>
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
</Grid.Background>
@@ -1127,7 +1147,7 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GroupRowHeightConverter}}"/>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
@@ -1197,4 +1217,4 @@
<Rectangle Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="Gray"/>
</Grid>
</AdornerDecorator>
</UserControl>
</UserControl>
@@ -4,8 +4,8 @@ using mbts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -14,7 +14,9 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
@@ -29,11 +31,6 @@ namespace bts.shell.pnl {
}
partial class PNLView : UserControl {
public static readonly double
MinGroupRowHeight = 24.0.RoundLayoutValue(),
MinHeaderRowHeight = 24.0.RoundLayoutValue();
DIYGridPopupManager _popup_man_backing;
public enum Columns {
Account = 1,
// Security
@@ -88,7 +85,6 @@ namespace bts.shell.pnl {
public static readonly IValueConverter
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
public static readonly IMultiValueConverter
@@ -167,22 +163,17 @@ namespace bts.shell.pnl {
void _grid_loaded(object sender, RoutedEventArgs e) {
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
rp.SetBinding(MarginProperty,
BindingHelper.create_multi(
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += row_height;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += row_height;
return new Thickness(0, top, 0, 0);
}),
new Binding(nameof(PNLVM.header_row_display)),
new Binding {
Path = new PropertyPath(DIYGrid.RowHeightProperty),
Converter = HeaderRowHeightConverter,
Source = _grid
}));
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += 24;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += 24;
return new Thickness(0, top, 0, 0);
}),
Source = DataContext
});
}
}
#endregion
@@ -249,15 +240,6 @@ namespace bts.shell.pnl {
}
#endregion
DIYGridPopupManager _popup_man {
get {
if (_popup_man_backing == null) {
_popup_man_backing = DIYGridPopupManager.create(this);
}
return _popup_man_backing;
}
}
void _cell_click(PNLCell cell) {
Log.Ui.info($"PNLCell Click: {cell}.");
try {
@@ -375,20 +357,41 @@ namespace bts.shell.pnl {
}
}
void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
var view = new HeaderedContentControl {
async void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
// vm
dynamic header = new ExpandoObject();
header.context = context_desc;
header.title = title_desc;
header.title_foreground = fg;
header.keep_open = false;
// view
var view = new PNLAuxiliaryPopupView {
Content = content,
Header = new AuxiliaryDataHeader {
context = context_desc,
title = title_desc,
title_foreground = fg
},
Header = header,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Resources = new AuxiliaryDataResources()
VerticalAlignment = VerticalAlignment.Top
};
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
_popup_man.show(view);
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
// popup adorner
// tho unlikely, better not assume target cell is non-null
var target_cell = _grid.ChildAtPoint<HyperlinkCell>(Mouse.GetPosition(_grid));
var popup_adorner = new PopupAdorner(_grid, target_cell ?? (FrameworkElement)_grid, view, HorizontalPosition.Middle, VerticalPosition.Bottom, 0, 0) {
OpenRelativeToMouse = target_cell == null,
EnableDragMove = true,
ClipToBounds = true
};
popup_adorner.SetBinding(PopupAdorner.StaysOpenProperty, new Binding("keep_open") { Source = header });
popup_adorner.PopupElement.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
popup_adorner.PopupElement.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => {
Log.assert(popup_adorner != null);
popup_adorner.detach();
popup_adorner = null;
}));
popup_adorner.attach();
popup_adorner.IsOpen = true;
FocusManager.SetIsFocusScope(popup_adorner.PopupElement, true);
popup_adorner.AdornedElement.Focus();
}
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
+15 -44
View File
@@ -653,54 +653,25 @@ namespace mbts.spark.utilities {
return def;
}
}
static CustomOrderDefinition.CustomOneofCase[] algo_config(
Func<bts.config.AlgoPermissions, List<string>> selector)
{
var conf = mbts.config.Configuration.get<bts.config.AlgoPermissions>();
return selector(conf).Select(x =>
{
return (CustomOrderDefinition.CustomOneofCase)System.Enum.Parse(typeof(CustomOrderDefinition.CustomOneofCase), x, true);
}).ToArray();
}
public static readonly CustomOrderDefinition.CustomOneofCase[]
// exchange_order_types are the types which can be submitted on an exchange security
exchange_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
spreader_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Spreader,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
attachable_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
};
exchange_order_types = algo_config(x => x.exchange_order_types),
spreader_order_types = algo_config(x => x.spreader_order_types),
attachable_order_types = algo_config(x => x.attachable_order_types),
outright_order_types = algo_config(x => x.outright_order_types);
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
public static void copy_values(CustomOrderDefinitionImmutable source, ref CustomOrderDefinition target) =>
@@ -54,13 +54,16 @@ namespace mbts.spark
{
readonly Func<TModel, TView> _convert;
readonly Func<TView, TModel> _convert_back;
readonly Func<IMessage, IMessage> _message_override;
TView _value;
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert, Func<TView, TModel> convert_back) :
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert,
Func<TView, TModel> convert_back, Func<IMessage, IMessage> message_override = null) :
base(field_descriptor)
{
_convert = convert;
_convert_back = convert_back;
_message_override = message_override;
}
public TView value
@@ -68,7 +71,8 @@ namespace mbts.spark
get => _value;
set
{
this.descriptor.Accessor.SetValue(this.message, _convert_back(_value = value));
this.descriptor.Accessor.SetValue( _message_override == null ? this.message : _message_override(this.message),
_convert_back(_value = value));
RaisePropertyChanged();
}
}
@@ -84,7 +88,7 @@ namespace mbts.spark
protected override void on_param_changed()
{
_value = _convert(ConvertUtils.try_convert<TModel>(this.descriptor.Accessor.GetValue(this.message)));
_value = _convert(ConvertUtils.try_convert<TModel>(this.descriptor.Accessor.GetValue(_message_override == null ? this.message : _message_override(this.message))));
RaisePropertyChanged(nameof(value));
}
}
@@ -163,7 +167,6 @@ namespace mbts.spark
RaisePropertyChanged();
}
}
public IObservable<QuantityMeasureImmutable?> subscribe_value()
{
return Observable.Create<QuantityMeasureImmutable?>(o =>
@@ -174,10 +177,9 @@ namespace mbts.spark
o.OnNext(quantity);
});
}
protected override void on_param_changed()
{
RaisePropertyChanged(nameof(quantity));
}
}
}
}
@@ -96,9 +96,41 @@ namespace mbts.spark {
{
Google.Protobuf.Reflection.MessageDescriptor desc = new TMutable().Descriptor;
_parameters.SyncWithDictionary(
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), _mk_param_vm);
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), x => _mk_param_vm(x));
add_message_params(desc);
}
}
void add_message_params(Google.Protobuf.Reflection.MessageDescriptor desc)
{
var msgs = desc.Fields.InDeclarationOrder().Where(x => !_is_value_param_desc(x) &&
x.MessageType.ClrType != typeof(CustomOrderDefinition) );
foreach(var message_field in msgs)
{
foreach (var fd in message_field.MessageType.Fields.InDeclarationOrder())
{
if(_is_value_param_desc(fd)) //one level deep
{
var mk = _mk_param_vm(fd,
outside_object =>
{
var msgfield = message_field.Accessor.GetValue(outside_object);
if(msgfield == null)
{
message_field.Accessor.SetValue(outside_object, Activator.CreateInstance(message_field.MessageType.ClrType));
msgfield = message_field.Accessor.GetValue(outside_object);
}
return (IMessage)msgfield;
});
if(mk == null)
continue;
if(!_parameters.Add(mk))
_parameters.Add(message_field.Name + "_" + mk.descriptor.Name, mk);
}
}
}
}
static bool _is_value_param_desc(FieldDescriptor desc)
{
return (desc.FieldType != Google.Protobuf.Reflection.FieldType.Message ||
@@ -117,7 +149,7 @@ namespace mbts.spark {
return v;
}
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor)
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
{
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
@@ -134,33 +166,36 @@ namespace mbts.spark {
return pvm;
}
else if (descriptor.FieldType == FieldType.Bool)
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault());
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float)
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
return mk<double, double>(descriptor, v => v, v => v, message_override, double.NaN);
else if (descriptor.FieldType == FieldType.Int32)
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
else if (descriptor.FieldType == FieldType.UInt32)
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
else if (descriptor.FieldType == FieldType.String)
return mk<string, string>(descriptor, v => v, v => v);
return mk<string, string>(descriptor, v => v, v => v, message_override);
else
return null;
}
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default)
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back,
Func<IMessage, IMessage> message_override = null, TView default_value = default)
{
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back, message_override);
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param,
message_override, default_value);
TView get_from_param(IMessage param, FieldDescriptor fd) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
void assign_to_param(IMessage param, FieldDescriptor fd, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
}
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param,
Func<IMessage, IMessage> message_override = null, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
{
add_property<T>(
pvm.descriptor.Name,
mdef => mdef != null ? selector(mdef, pvm.descriptor) : default_value,
(mdef, v) => assign_to_param(mdef, pvm.descriptor, v),
mdef => mdef != null ? selector( message_override == null ? mdef : message_override(mdef), pvm.descriptor) : default_value,
(mdef, v) => assign_to_param(message_override == null ? mdef : message_override(mdef), pvm.descriptor, v),
() => pvm.value,
v => pvm.value = v,
pvm.subscribe_value(),
@@ -4,14 +4,14 @@ using OffsetDefi = bts.api.orderman.OffsetOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class OffsetOrderVM : OrderTypeVMBase<OffsetDef, OffsetDefi> {
readonly Property<uint?> _tick_offset_on_deploy;
readonly Property<int?> _tick_offset_on_deploy;
public OffsetOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_tick_offset_on_deploy = add_nullable_property(nameof(tick_offset_on_deploy), OffsetDef.TickOffsetOnDeployFieldNumber, 0u);
_tick_offset_on_deploy = add_nullable_property(nameof(tick_offset_on_deploy), OffsetDef.TickOffsetOnDeployFieldNumber, 0);
}
public uint? tick_offset_on_deploy { get => _tick_offset_on_deploy.Value; set => _tick_offset_on_deploy.set(value); }
public int? tick_offset_on_deploy { get => _tick_offset_on_deploy.Value; set => _tick_offset_on_deploy.set(value); }
}
}
}
+1 -2
View File
@@ -9,7 +9,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
<PlatformTarget>AnyCPU</PlatformTarget>
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
<ApplicationIcon>logo.ico</ApplicationIcon>
@@ -35,7 +34,7 @@
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
<PackageReference Include="IronPython" Version="2.7.7" />
<PackageReference Include="NAudio" Version="1.8.4" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
-1
View File
@@ -41,7 +41,6 @@
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DiscreteValuesFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/TimeFilterControl.xaml"/>
<ResourceDictionary Source="/view/Controls/ActionMenu/ActionMenu.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
@@ -70,21 +70,5 @@ namespace mbts.ui {
}, () => Disposable.dispose(ref can_execute_sub));
return cmd;
}
public static ICommand create<T, TResult>(Action<TResult> execute, Func<T, bool> get_can_execute, Func<T, TResult> convert, out IObserver<T> observer) {
TResult param = default;
bool can_execute = false;
var cmd = new DelegateCommand(() => execute(param), () => can_execute);
IDisposable can_execute_sub = null;
observer = new AnonymousObserver<T>(val => {
var could_execute = can_execute;
can_execute = get_can_execute(val);
if (can_execute)
param = convert(val);
if (can_execute != could_execute)
cmd.RaiseCanExecuteChanged();
}, () => Disposable.dispose(ref can_execute_sub));
return cmd;
}
}
}
@@ -34,9 +34,6 @@ namespace mbts {
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
// TODO: replace refs to IsNaNOrInfinity with IsReal (it's a shorter name, and just as expressive)
public static bool IsReal(this double d) => !double.IsNaN(d) && !double.IsInfinity(d);
public static bool IsRealNonZero(this double d) => d != 0 && !double.IsNaN(d) && !double.IsInfinity(d);
public static double CoerceNaN(double value) => CoerceNaN(value, 0.0);
public static double CoerceNaN(double value, double if_nan) => double.IsNaN(value) ? if_nan : value;
public static double CoerceNaNOrInfinity(double value) => CoerceNaNOrInfinity(value, 0.0);
@@ -131,11 +131,6 @@ namespace mbts.ui.native
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out Win32Point pt);
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
public static readonly IntPtr HWND_TOP = new IntPtr(0);
public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwnd_insert_after, int x, int y, int cx, int cy, SWP flags);
@@ -260,20 +255,7 @@ namespace mbts.ui.native
{
SWP_NOSIZE = 1,
SWP_NOMOVE = 2,
SWP_NOZORDER = 0x0004,
SWP_NOREDRAW = 0x0008,
SWP_NOACTIVATE = 0x0010,
// The frame changed: send WM_NCCALCSIZE
SWP_FRAMECHANGED = 0x0020,
SWP_SHOWWINDOW = 0x0040,
SWP_HIDEWINDOW = 0x0080,
SWP_NOCOPYBITS = 0x0100,
// Don’t do owner Z ordering
SWP_NOOWNERZORDER = 0x0200,
// Don’t send WM_WINDOWPOSCHANGING
SWP_NOSENDCHANGING = 0x0400,
TOPMOST_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING
SWP_FRAMECHANGED = 0x20
}
public enum SPI : uint
@@ -138,7 +138,6 @@ namespace mbts.ui.native
NCMBUTTONDOWN = 0x00A7,
NCMBUTTONUP = 0x00A8,
NCMOUSEMOVE = 0x00A0,
NCMOUSELEAVE = 0x02A2,
NCPAINT = 0x0085,
NCRBUTTONDBLCLK = 0x00A6,
NCRBUTTONDOWN = 0x00A4,
@@ -15,7 +15,6 @@ namespace bts.settings {
// determine the various windows to which such rules apply.
interface IFillsGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface ITimeAndSalesGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
@@ -26,8 +26,7 @@ namespace mbts.persist {
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs)),
_time_and_sales_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.time_and_sales_grid, (us, gs) => us.with(time_and_sales: gs));
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
@@ -39,7 +38,6 @@ namespace mbts.persist {
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
readonly IEnumerable<ITimeAndSalesGridSettingsMigrationRule> _time_and_sales_migration_rules;
readonly IUserPersist _persist;
UserSettings _default_settings;
@@ -51,8 +49,7 @@ namespace mbts.persist {
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules,
[ImportMany]IEnumerable<ITimeAndSalesGridSettingsMigrationRule> time_and_sales_migration_rules) {
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
//
_persist = persist;
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
@@ -60,7 +57,6 @@ namespace mbts.persist {
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
_time_and_sales_migration_rules = time_and_sales_migration_rules ?? Enumerable.Empty<ITimeAndSalesGridSettingsMigrationRule>();
init();
async void init() {
UserSettings s;
@@ -72,8 +68,7 @@ namespace mbts.persist {
_mview_migration_rules.merge().wrap(_mview_propi),
_pandl_migration_rules.merge().wrap(_pandl_propi),
_pnl_migration_rules.merge().wrap(_pnl_propi),
_synth_migration_rules.merge().wrap(_synth_propi),
_time_and_sales_migration_rules.merge().wrap(_time_and_sales_propi)
_synth_migration_rules.merge().wrap(_synth_propi)
};
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
// TODO: move ladder sanitization logic into ISettingsMigrationRule
@@ -153,12 +148,6 @@ namespace mbts.persist {
_synth_migration_rules,
_synth_propi);
public IWindowSettingsManager<GridSettings> create_time_and_sales_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_time_and_sales_migration_rules,
_time_and_sales_propi);
public async Task save_default(UserSettings value) {
// notify observers before persisting so the changes are reflected immediately in the ui
lock (_default_settings_lock) {
+1 -15
View File
@@ -5,23 +5,9 @@
xmlns:ui="clr-namespace:mbts.ui"
>
<!--Control Base Colors-->
<Color x:Key="Control.SuperLightGrayColor">#F6F6F6</Color>
<Color x:Key="Control.LightGrayColor">#E5E5E5</Color>
<Color x:Key="Control.LightMediumGrayColor">#CFCFCF</Color>
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
<Color x:Key="Control.MediumDarkGrayColor">#ACACAC</Color>
<Color x:Key="Control.AlphaLightGrayColor">#10000000</Color>
<Color x:Key="Control.AlphaLightMediumGrayColor">#24000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor">#3A000000</Color>
<Color x:Key="Control.AlphaMediumGrayColor2">#FF0000</Color>
<Color x:Key="Control.DarkMediumGrayColor">#FF959595</Color>
<Color x:Key="Control.DarkGrayColor">#707070</Color>
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
<SolidColorBrush x:Key="Control.AlphaLightGrayBrush" Color="{StaticResource Control.AlphaLightGrayColor}"/>
<SolidColorBrush x:Key="Control.AlphaLightMediumGrayBrush" Color="{StaticResource Control.AlphaLightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.LightGrayBrush" Color="{StaticResource Control.LightGrayColor}"/>
<SolidColorBrush x:Key="Control.LightMediumGrayBrush" Color="{StaticResource Control.LightMediumGrayColor}"/>
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
@@ -144,4 +130,4 @@
<SolidColorBrush x:Key="ListBoxItem.Selected.Inactive.Border" Color="{StaticResource ListBoxItem.Selected.Inactive.Border.Color}"/>
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Background" Color="{StaticResource ListBoxItem.MouseOver.Background.Color}"/>
<SolidColorBrush x:Key="ListBoxItem.MouseOver.Border" Color="{StaticResource ListBoxItem.MouseOver.Border.Color}"/>
</ResourceDictionary>
</ResourceDictionary>
@@ -1,102 +1,78 @@
<ResourceDictionary
<c:PopupControl
x:Class="mbts.ui.controls.ActionMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
Margin="1"
Background="Transparent"
BorderBrush="Transparent"
Padding="2"
PopupPlacement="Bottom"
ShowExpanderArrow="False"
Cursor="Hand"
>
<SolidColorBrush x:Key="SeparatorFill" Color="{DynamicResource Control.AlphaMediumGrayColor}"/>
<ControlTemplate x:Key="ActionMenuTemplate" TargetType="c:ActionMenu">
<Border
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver" Storyboard="{DynamicResource Button.MouseOver.Storyboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<c:PopupControl.Resources>
<CollectionViewSource x:Key="ItemsSourceView" Source="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=c:ActionMenu}}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="group"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</c:PopupControl.Resources>
<c:PopupControl.Header>
<Border Width="16">
<Grid>
<Button
x:Name="button"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="2.5,2"
Style="{DynamicResource SparkToolBarButton}"
>
<Button.Background>
<SolidColorBrush/>
</Button.Background>
</Button>
<Popup c:PopupViewController.ButtonSource="{Binding ElementName=button}" Placement="Bottom" PlacementTarget="{Binding ElementName=button}" StaysOpen="False">
<Border Background="{DynamicResource Control.LightGrayBrush}" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource Control.DarkMediumGrayColor}"/>
</Border.BorderBrush>
<ItemsPresenter/>
</Border>
</Popup>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="Black" Height="2" HorizontalAlignment="Stretch"/>
<Rectangle Grid.Row="1" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
<Rectangle Grid.Row="2" Fill="Black" Height="2" Margin="0,4,0,0" HorizontalAlignment="Stretch"/>
</Grid>
</Border>
</ControlTemplate>
<DataTemplate x:Key="HamburgerButtonTemplate">
<Image
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{x:Static u:Icons.HamburgerMenu}"
/>
</DataTemplate>
<Style x:Key="ActionMenuBase" TargetType="c:ActionMenu">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush/>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{StaticResource ActionMenuTemplate}"/>
</Style>
<DataTemplate DataType="{x:Type u:MenuItemVM}">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
<Style TargetType="c:ActionMenu" BasedOn="{StaticResource ActionMenuBase}">
<Setter Property="Panel.ZIndex" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Margin" Value="1"/>
<Setter Property="HeaderTemplate" Value="{StaticResource HamburgerButtonTemplate}"/>
<Setter Property="Width" Value="22"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="MenuItem">
<Setter Property="MinWidth" Value="167"/>
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Header" Value="{Binding name}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={x:Static cv:ImageSourceIconConverter.Instance}}"/>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</c:PopupControl.Header>
<Menu ItemsSource="{Binding Source={StaticResource ItemsSourceView}}">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<StackPanel>
<ItemsPresenter/>
<Rectangle HorizontalAlignment="Stretch" Height="1" Margin="22,0,0,0" Fill="LightGray"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</Menu.GroupStyle>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<!--NOTE: CommandParameter binding must be declared BEFORE Command,
otherwise, CanExecuted is called before the parameter value is realized...I know, it sucks...-->
<Setter Property="CommandParameter" Value="{Binding parameter}"/>
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Header" Value="{Binding name}"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
<Setter Property="MinWidth" Value="167"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
</c:PopupControl>
@@ -1,15 +1,22 @@
using System.Windows;
using System.Windows.Controls;
using System.Collections;
using System.Linq;
using System.Windows;
namespace mbts.ui.controls {
/// <summary>
/// Interaction logic for ActionMenu.xaml
/// </summary>
class ActionMenu : HeaderedItemsControl {
static ActionMenu() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionMenu), new FrameworkPropertyMetadata(typeof(ActionMenu)));
partial class ActionMenu : PopupControl {
public ActionMenu() =>
InitializeComponent();
protected override DependencyObject GetContainerForItemOverride() =>
new MenuItem();
public static readonly DependencyProperty ItemsSourceProperty =
WPFHelper.create_dp<IEnumerable, ActionMenu>(
nameof(ItemsSource), Enumerable.Empty<IMenuItemVM>());
public IEnumerable ItemsSource {
get => GetValue(ItemsSourceProperty) as IEnumerable;
set => SetValue(ItemsSourceProperty, value);
}
}
}
@@ -1,59 +0,0 @@
using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
class AdornerHost : Adorner {
UIElement _child;
public AdornerHost(UIElement adorned_element)
: base(adorned_element) { }
public static readonly DependencyProperty ChildProperty =
WPFHelper.create_dp<UIElement, AdornerHost>(
nameof(Child), (o, v) => {
if (o._child != null)
o.RemoveVisualChild(o._child);
o._child = v;
if (v != null)
o.AddVisualChild(v);
});
public UIElement Child {
get => _child;
set => SetValue(ChildProperty, value);
}
protected override int VisualChildrenCount => _child != null ? 1 : 0;
protected override Visual GetVisualChild(int index) =>
index == 0 && _child != null ? _child : throw new ArgumentOutOfRangeException();
protected override Size ArrangeOverride(Size finalSize) {
if (_child == null) {
return base.ArrangeOverride(finalSize);
}
_child.Arrange(new Rect(finalSize));
return _child.DesiredSize;
}
protected override Size MeasureOverride(Size constraint) {
if (_child == null) {
return base.MeasureOverride(constraint);
}
_child.Measure(constraint);
return _child.DesiredSize;
}
}
class AdornerHost<TChild> : AdornerHost where TChild : UIElement {
public AdornerHost(UIElement adorned_element)
: base(adorned_element) { }
public new TChild Child {
get => base.Child as TChild;
set => base.Child = value;
}
}
}
@@ -1,136 +0,0 @@
using mbts.ui.dpi;
using mbts.ui.extensions;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
class DIYGridCellHoverAdorner : Adorner, IDisposable {
Brush _row_fill, _cell_fill;
Pen _row_stroke, _cell_stroke;
ScrollViewer _scroll_viewer;
ScrollContentPresenter _scroll_content;
DIYGrid _grid;
IDisposable _dispose;
DIYGrid.CellGeometry? _hover_cell;
AdornerHost _menu_host = null;
ActionMenu _menu => _menu_host.Child as ActionMenu;
#region RowFill
public static readonly DependencyProperty RowFillProperty =
WPFHelper.create_dp<Brush, DIYGridCellHoverAdorner>(
nameof(RowFill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._row_fill = v,
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
public SolidColorBrush RowFill {
get => GetValue(RowFillProperty) as SolidColorBrush;
set => SetValue(RowFillProperty, value);
}
#endregion
#region RowStroke
public static readonly DependencyProperty RowStrokeProperty =
WPFHelper.create_dp<Pen, DIYGridCellHoverAdorner>(
nameof(RowStroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._row_stroke = v);
public Pen RowStroke {
get => GetValue(RowStrokeProperty) as Pen;
set => SetValue(RowStrokeProperty, value);
}
#endregion
#region CellFill
public static readonly DependencyProperty CellFillProperty =
WPFHelper.create_dp<Brush, DIYGridCellHoverAdorner>(
nameof(CellFill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._cell_fill = v);
public SolidColorBrush CellFill {
get => GetValue(CellFillProperty) as SolidColorBrush;
set => SetValue(CellFillProperty, value);
}
#endregion
#region CellStroke
public static readonly DependencyProperty CellStrokeProperty =
WPFHelper.create_dp<Pen, DIYGridCellHoverAdorner>(
nameof(CellStroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._cell_stroke = v);
public Pen CellStroke {
get => GetValue(CellStrokeProperty) as Pen;
set => SetValue(CellStrokeProperty, value);
}
#endregion
public DIYGridCellHoverAdorner(ListBox list_box, ScrollViewer scroll_viewer)
: this(list_box, scroll_viewer, list_box.descendants_of_type<ScrollContentPresenter>().FirstOrDefault()) { }
/// <summary>
/// Create an instance of the <see cref="DIYGridCellHoverAdorner"/> class.
/// </summary>
/// <param name="list_box"></param>
/// <param name="scroll_viewer"></param>
/// <param name="scroll_content">
/// The actual AdornedElement (the ScrollViewer may clip the ScrollContentPresenter, and the most prudent way to reflect such a clip is to use it as the actual AdornedElement).
/// </param>
public DIYGridCellHoverAdorner(ListBox list_box, ScrollViewer scroll_viewer, ScrollContentPresenter scroll_content)
: base(scroll_content) {
_row_fill = RowFill;
_row_stroke = RowStroke;
_cell_fill = CellFill;
_cell_stroke = CellStroke;
_scroll_viewer = scroll_viewer;
_scroll_content = scroll_content;
IsHitTestVisible = false;
// reflect AdornedElement's clip
IsClipEnabled = true;
_grid = list_box.find_ancestor_of_type<DIYGrid>();
_dispose = Disposable.Create(
_grid.SubscribeMouseHoverCell().Subscribe(cell => {
_hover_cell = cell;
if (_hover_cell.HasValue) {
var (row, col) = _hover_cell.Value;
var row_tl = row.TranslatePoint(default, this);
}
InvalidateVisual();
}));
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
if (_hover_cell.HasValue) {
(var row, var cell) = _hover_cell.Value;
if (row != null) {
var row_tl = new Lazy<Point>(() => row.TranslatePoint(default, this));
if (_row_fill != null || _row_stroke != null) {
var row_rect = new Rect(row_tl.Value, row.RenderSize);
if (_scroll_content != null) {
var content_bounds = this.GetChildBounds(_scroll_content);
row_rect.Intersect(content_bounds);
}
drawingContext.DrawRectangle(_row_fill, _row_stroke, row_rect, true);
}
if (_cell_fill != null || _cell_stroke != null) {
var cell_rect = cell.to_visible_rect(row.ActualHeight, row_tl.Value.Y);
drawingContext.DrawRectangle(_cell_fill, _cell_stroke, cell_rect, true);
}
}
}
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
@@ -24,7 +24,7 @@ namespace mbts.ui.controls.adorners {
nameof(Fill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._fill = v,
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
DIYGrid.DefaultSelectionHighlight);
public SolidColorBrush Fill {
get => GetValue(FillProperty) as SolidColorBrush;
@@ -38,7 +38,7 @@ namespace mbts.ui.controls.adorners {
nameof(Stroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._stroke = v,
FrozenPenCache.get_pen(DIYGrid.DefaultSelectionHighlight));
DIYGrid.DefaultSelectionStroke);
public Pen Stroke {
get => GetValue(StrokeProperty) as Pen;
@@ -8,8 +8,8 @@ using System.Windows.Media;
namespace mbts.ui.controls.adorners {
public class DragRectAdorner : Adorner, IDisposable {
Brush _fill;
Pen _stroke;
Brush _fill = DIYGrid.DefaultSelectionHighlight;
Pen _stroke = DIYGrid.DefaultSelectionStroke;
readonly ScrollViewerTuple _scroll_viewer_tup;
readonly double _drift_multiplier;
Point? _drag_start;
@@ -20,7 +20,7 @@ namespace mbts.ui.controls.adorners {
nameof(Fill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._fill = v,
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
DIYGrid.DefaultSelectionHighlight);
public SolidColorBrush Fill {
get => GetValue(FillProperty) as SolidColorBrush;
@@ -32,7 +32,7 @@ namespace mbts.ui.controls.adorners {
nameof(Stroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._stroke = v,
FrozenPenCache.get_pen(DIYGrid.DefaultSelectionHighlight));
DIYGrid.DefaultSelectionStroke);
public Pen Stroke {
get => GetValue(StrokeProperty) as Pen;
@@ -48,8 +48,6 @@ namespace mbts.ui.controls.adorners {
/// <param name="scroll_viewer">The element's ScrollViewer.</param>
/// <param name="drift_multiplier">The number of pixels per scroll unit.</param>
public DragRectAdorner(FrameworkElement adorned_element, ScrollViewer scroll_viewer = null, double drift_multiplier = 1) : base(adorned_element) {
_fill = Fill;
_stroke = Stroke;
_drift_multiplier = drift_multiplier;
AdornedElement.PreviewMouseDown += _on_preview_mouse_down;
AdornedElement.PreviewMouseMove += _on_preview_mouse_move;
@@ -1,18 +1,22 @@
using mbts.ui.extensions;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using bts.utils;
using mbts.ui.extensions;
namespace mbts.ui.controls.adorners {
class RowSelectionAdorner : Adorner, IDisposable {
Brush _fill;
Pen _stroke;
ListBox _diy_rp;
public class RowSelectionAdorner : Adorner, DIYGrid.ISelectionHighlightListener, IDisposable {
Brush _fill = DIYGrid.DefaultSelectionHighlight;
Pen _stroke = DIYGrid.DefaultSelectionStroke;
DIYGridRowsPresenter _diy_rp;
ScrollViewer _diy_scroll;
ScrollContentPresenter _diy_scp;
List<IDisposable> _disposables = new List<IDisposable>();
#region Fill
@@ -21,21 +25,20 @@ namespace mbts.ui.controls.adorners {
nameof(Fill),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._fill = v,
FrozenBrushCache.get_brush(DIYGrid.DefaultSelectionHighlight));
DIYGrid.DefaultSelectionHighlight);
public SolidColorBrush Fill {
get => GetValue(FillProperty) as SolidColorBrush;
set => SetValue(FillProperty, value);
}
#endregion
#region Stroke
public static readonly DependencyProperty StrokeProperty =
WPFHelper.create_dp<Pen, RowSelectionAdorner>(
nameof(Stroke),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._stroke = v,
FrozenPenCache.get_pen(DIYGrid.DefaultSelectionHighlight.with(255)));
DIYGrid.DefaultSelectionStroke);
public Pen Stroke {
get => GetValue(StrokeProperty) as Pen;
@@ -43,28 +46,13 @@ namespace mbts.ui.controls.adorners {
}
#endregion
public RowSelectionAdorner(ListBox list_box, ScrollViewer scroll_viewer)
: this(list_box, scroll_viewer, list_box.descendants_of_type<ScrollContentPresenter>().FirstOrDefault()) { }
/// <summary>
/// Create an instance of the <see cref="RowSelectionAdorner"/> class.
/// </summary>
/// <param name="list_box"></param>
/// <param name="scroll_viewer"></param>
/// <param name="scroll_presenter">
/// The actual AdornedElement (the ScrollViewer may clip the ScrollContentPresenter, and the most prudent way to reflect such a clip is to use it as the actual AdornedElement).
/// </param>
public RowSelectionAdorner(ListBox list_box, ScrollViewer scroll_viewer, ScrollContentPresenter scroll_presenter)
: base(scroll_presenter) {
_fill = Fill;
_stroke = Stroke;
internal RowSelectionAdorner(DIYGridRowsPresenter adorned_element, ScrollViewer scroll_viewer) : base(adorned_element) {
// we want our clicks to go through to the underlying grid
IsHitTestVisible = false;
// reflect the AdornedElement's clip
IsClipEnabled = true;
_diy_rp = list_box;
_diy_rp = adorned_element;
_diy_scroll = scroll_viewer;
_diy_scp = _diy_rp.descendants_of_type<ScrollContentPresenter>().empty_if_null().FirstOrDefault();
// We actually don't need mouse up/down or key up/down when we can just respond to selection changes
_disposables.Add(((System.Windows.Controls.Primitives.Selector)_diy_rp).SubscribeSelectionChanged((s, e) => InvalidateVisual()));
@@ -72,24 +60,25 @@ namespace mbts.ui.controls.adorners {
_disposables.Add(_diy_scroll.SubscribeScrollChanged((s, e) => InvalidateVisual()));
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
if (_diy_rp.SelectedItems != null && _diy_rp.SelectedItems.Count > 0) {
foreach(var lbi in _diy_rp.SelectedItems) {
if (AdornedElement is ListBox lb && lb.SelectedItems != null && lb.SelectedItems.Count > 0) {
foreach(var lbi in lb.SelectedItems) {
if (_diy_rp.ItemContainerGenerator.ContainerFromItem(lbi) is FrameworkElement row && row.IsVisible) {
var top_left = _diy_rp.GetChildTopLeft(row);
var top_left = lb.GetChildTopLeft(row);
var top_is_within_bounds = top_left.Y >= 0;
// Note that the top of the row can still technically be visible but we wont show
// the adorner rectangle b/c the bottom of the row will be obscured by the scrollbar
var bottom_is_within_bounds = _diy_rp.GetChildBounds(row).BottomRight.Y <= _diy_scroll.ActualHeight;
var bottom_is_within_bounds = lb.GetChildBounds(row).BottomRight.Y <= _diy_scroll.ActualHeight;
if (top_is_within_bounds && bottom_is_within_bounds) {
// we use the scroll content presenter in the X point calculation instead of the row
// because of the OB which has a right side panel that takes up some space over
// row width
drawingContext.DrawRectangle(_fill,
_stroke,
new Rect(top_left, new Point(top_left.X + AdornedElement.RenderSize.Width, top_left.Y + row.ActualHeight)));
new Rect(top_left, new Point(top_left.X + _diy_scp.ActualWidth, top_left.Y + row.ActualHeight)));
}
}
}
@@ -1,493 +0,0 @@
<ResourceDictionary
x:Class="mbts.ui.controls.AutoHideScrollbarResources"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<sys:Double x:Key="ScrollBarCollapsedWidth">5</sys:Double>
<sys:Double x:Key="ScrollBarCollapsedHeight">5</sys:Double>
<!--Thumb Colors-->
<Color x:Key="ThumbStaticColor">#FFCDCDCD</Color>
<Color x:Key="ThumbMouseOverColor">#FFA6A6A6</Color>
<Color x:Key="ThumbPressedColor">#FF606060</Color>
<!--Line Button Colors-->
<Color x:Key="LineButtonStaticBackgroundColor">#FFF0F0F0</Color>
<Color x:Key="LineButtonMouseOverBackgroundColor">#FFDADADA</Color>
<Color x:Key="LineButtonPressedBackgroundColor">#FF606060</Color>
<Color x:Key="LineButtonStaticForegroundColor">#FF606060</Color>
<Color x:Key="LineButtonMouseOverForegroundColor">#FF000000</Color>
<Color x:Key="LineButtonPressedForegroundColor">#FFFFFFFF</Color>
<Color x:Key="LineButtonDisabledForegroundColor">#FFBFBFBF</Color>
<!--Track-->
<Color x:Key="TrackStaticBackgroundColor">#FFF0F0F0</Color>
<sys:String x:Key="ArrowUpGeometry">
M0,4C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4z
</sys:String>
<sys:String x:Key="ArrowDownGeometry">
M0,2.5C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5z
</sys:String>
<Style x:Key="ScrollBarTrackThumb" TargetType="Thumb">
<Setter Property="Focusable" Value="False"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Rectangle
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PageButton" TargetType="RepeatButton">
<!--<Setter Property="Background" Value="#FFF0F0F0"/>-->
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Rectangle
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{TemplateBinding Background}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LineButton" TargetType="RepeatButton">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{StaticResource LineButtonStaticForegroundColor}"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Border
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource LineButtonMouseOverBackgroundColor}"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource LineButtonPressedBackgroundColor}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border.Background>
<SolidColorBrush Color="{StaticResource LineButtonStaticBackgroundColor}"/>
</Border.Background>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{StaticResource LineButtonMouseOverForegroundColor}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{StaticResource LineButtonPressedForegroundColor}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="HorizontalScrollBarTemplate" TargetType="ScrollBar">
<Grid
Background="Transparent"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Height" BeginTime="00:00:01" Duration="00:00:00.25" To="{StaticResource ScrollBarCollapsedHeight}"/>
<DoubleAnimation Storyboard.TargetName="PART_TrackBackground" Storyboard.TargetProperty="Opacity" BeginTime="00:00:01.75" Duration="00:00:00.20" To="0"/>
<!--Line Up-->
<DoubleAnimation Storyboard.TargetName="PART_LineLeftButton" Storyboard.TargetProperty="Height" BeginTime="00:00:01" Duration="00:00:00.25" To="{StaticResource ScrollBarCollapsedHeight}"/>
<DoubleAnimation Storyboard.TargetName="PART_LineLeftButton" Storyboard.TargetProperty="Opacity" BeginTime="00:00:01.75" Duration="00:00:00.20" To="0"/>
<!--Line Down-->
<DoubleAnimation Storyboard.TargetName="PART_LineRightButton" Storyboard.TargetProperty="Height" BeginTime="00:00:01" Duration="00:00:00.25" To="{StaticResource ScrollBarCollapsedHeight}"/>
<DoubleAnimation Storyboard.TargetName="PART_LineRightButton" Storyboard.TargetProperty="Opacity" BeginTime="00:00:01.75" Duration="00:00:00.20" To="0"/>
</Storyboard>
</VisualState>
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Height" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.HorizontalScrollBarHeight}"/>
<DoubleAnimation Storyboard.TargetName="PART_TrackBackground" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<!--Line Up-->
<DoubleAnimation Storyboard.TargetName="PART_LineLeftButton" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineLeftButton" Storyboard.TargetProperty="Height" BeginTime="00:00:00" Duration="00:00:00.15" To="{x:Static SystemParameters.HorizontalScrollBarHeight}"/>
<!--Line Down-->
<DoubleAnimation Storyboard.TargetName="PART_LineRightButton" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineRightButton" Storyboard.TargetProperty="Height" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.HorizontalScrollBarHeight}"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Height" BeginTime="0" Duration="0" To="{x:Static SystemParameters.HorizontalScrollBarHeight}"/>
<DoubleAnimation Storyboard.TargetName="PART_TrackBackground" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<!--Line Up-->
<DoubleAnimation Storyboard.TargetName="PART_LineLeftButton" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineLeftButton" Storyboard.TargetProperty="Height" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.HorizontalScrollBarHeight}"/>
<!--Line Down-->
<DoubleAnimation Storyboard.TargetName="PART_LineRightButton" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineRightButton" Storyboard.TargetProperty="Height" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.HorizontalScrollBarHeight}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="{x:Static SystemParameters.HorizontalScrollBarButtonWidth}"/>
<ColumnDefinition Width="0.00001*"/>
<ColumnDefinition Width="*" MaxWidth="{x:Static SystemParameters.HorizontalScrollBarButtonWidth}"/>
</Grid.ColumnDefinitions>
<RepeatButton
x:Name="PART_LineLeftButton"
Command="{x:Static ScrollBar.LineLeftCommand}"
Focusable="False"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Opacity="0"
Height="{StaticResource ScrollBarCollapsedHeight}"
Style="{StaticResource LineButton}"
>
<Path
x:Name="ArrowUp"
Data="{Binding Source={StaticResource ArrowUpGeometry}}"
Fill="{Binding Parent.Foreground, RelativeSource={RelativeSource Self}}"
/>
</RepeatButton>
<Track
x:Name="PART_Track"
Grid.Column="1"
IsDirectionReversed="True"
Focusable="False"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Height="{StaticResource ScrollBarCollapsedHeight}"
>
<Track.Thumb>
<Thumb x:Name="Thumb" Style="{StaticResource ScrollBarTrackThumb}">
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource ThumbMouseOverColor}"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource ThumbPressedColor}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Thumb.Background>
<SolidColorBrush Color="{StaticResource ThumbStaticColor}"/>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Command="ScrollBar.PageRightCommand" Style="{StaticResource PageButton}"/>
</Track.IncreaseRepeatButton>
<Track.DecreaseRepeatButton>
<RepeatButton Command="ScrollBar.PageLeftCommand" Style="{StaticResource PageButton}"/>
</Track.DecreaseRepeatButton>
</Track>
<Rectangle
x:Name="PART_TrackBackground"
Grid.Column="1"
Panel.ZIndex="-1"
Focusable="False"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
IsHitTestVisible="False"
Opacity="0"
Height="{Binding ActualHeight, ElementName=PART_Track}"
>
<Shape.Fill>
<SolidColorBrush Color="{StaticResource TrackStaticBackgroundColor}"/>
</Shape.Fill>
</Rectangle>
<RepeatButton
x:Name="PART_LineRightButton"
Grid.Column="2"
Command="{x:Static ScrollBar.LineRightCommand}"
Focusable="False"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Opacity="0"
Height="{StaticResource ScrollBarCollapsedHeight}"
Style="{StaticResource LineButton}"
>
<Path
x:Name="ArrowDown"
Data="{Binding Source={StaticResource ArrowDownGeometry}}"
Fill="{Binding Parent.Foreground, RelativeSource={RelativeSource Self}}"
/>
</RepeatButton>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Thumb" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="VerticalScrollBarTemplate" TargetType="ScrollBar">
<Grid
Background="Transparent"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Width" BeginTime="00:00:01" Duration="00:00:00.25" To="{StaticResource ScrollBarCollapsedWidth}"/>
<DoubleAnimation Storyboard.TargetName="PART_TrackBackground" Storyboard.TargetProperty="Opacity" BeginTime="00:00:01.75" Duration="00:00:00.20" To="0"/>
<!--Line Up-->
<DoubleAnimation Storyboard.TargetName="PART_LineUpButton" Storyboard.TargetProperty="Width" BeginTime="00:00:01" Duration="00:00:00.25" To="{StaticResource ScrollBarCollapsedWidth}"/>
<DoubleAnimation Storyboard.TargetName="PART_LineUpButton" Storyboard.TargetProperty="Opacity" BeginTime="00:00:01.75" Duration="00:00:00.20" To="0"/>
<!--Line Down-->
<DoubleAnimation Storyboard.TargetName="PART_LineDownButton" Storyboard.TargetProperty="Width" BeginTime="00:00:01" Duration="00:00:00.25" To="{StaticResource ScrollBarCollapsedWidth}"/>
<DoubleAnimation Storyboard.TargetName="PART_LineDownButton" Storyboard.TargetProperty="Opacity" BeginTime="00:00:01.75" Duration="00:00:00.20" To="0"/>
</Storyboard>
</VisualState>
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Width" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.VerticalScrollBarWidth}"/>
<DoubleAnimation Storyboard.TargetName="PART_TrackBackground" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<!--Line Up-->
<DoubleAnimation Storyboard.TargetName="PART_LineUpButton" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineUpButton" Storyboard.TargetProperty="Width" BeginTime="00:00:00" Duration="00:00:00.15" To="{x:Static SystemParameters.VerticalScrollBarWidth}"/>
<!--Line Down-->
<DoubleAnimation Storyboard.TargetName="PART_LineDownButton" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineDownButton" Storyboard.TargetProperty="Width" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.VerticalScrollBarWidth}"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Width" BeginTime="0" Duration="0" To="{x:Static SystemParameters.VerticalScrollBarWidth}"/>
<DoubleAnimation Storyboard.TargetName="PART_TrackBackground" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<!--Line Up-->
<DoubleAnimation Storyboard.TargetName="PART_LineUpButton" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineUpButton" Storyboard.TargetProperty="Width" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.VerticalScrollBarWidth}"/>
<!--Line Down-->
<DoubleAnimation Storyboard.TargetName="PART_LineDownButton" Storyboard.TargetProperty="Opacity" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="PART_LineDownButton" Storyboard.TargetProperty="Width" BeginTime="0" Duration="00:00:00.15" To="{x:Static SystemParameters.VerticalScrollBarWidth}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="*" MaxHeight="{x:Static SystemParameters.VerticalScrollBarButtonHeight}"/>
<RowDefinition Height="0.00001*"/>
<RowDefinition Height="*" MaxHeight="{x:Static SystemParameters.VerticalScrollBarButtonHeight}"/>
</Grid.RowDefinitions>
<RepeatButton
x:Name="PART_LineUpButton"
Command="{x:Static ScrollBar.LineUpCommand}"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Opacity="0"
Width="{StaticResource ScrollBarCollapsedWidth}"
Style="{StaticResource LineButton}"
>
<Path
x:Name="ArrowUp"
Data="{Binding Source={StaticResource ArrowUpGeometry}}"
Fill="{Binding Parent.Foreground, RelativeSource={RelativeSource Self}}"
/>
</RepeatButton>
<Track
x:Name="PART_Track"
Grid.Row="1"
IsDirectionReversed="True"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Width="{StaticResource ScrollBarCollapsedWidth}"
>
<Track.Thumb>
<Thumb x:Name="Thumb" Style="{StaticResource ScrollBarTrackThumb}">
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource ThumbMouseOverColor}"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource ThumbPressedColor}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Thumb.Background>
<SolidColorBrush Color="{StaticResource ThumbStaticColor}"/>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Command="ScrollBar.PageDownCommand" Style="{StaticResource PageButton}"/>
</Track.IncreaseRepeatButton>
<Track.DecreaseRepeatButton>
<RepeatButton Command="ScrollBar.PageUpCommand" Style="{StaticResource PageButton}"/>
</Track.DecreaseRepeatButton>
</Track>
<Rectangle
x:Name="PART_TrackBackground"
Grid.Row="1"
Panel.ZIndex="-1"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsHitTestVisible="False"
Opacity="0"
Width="{Binding ActualWidth, ElementName=PART_Track}"
>
<Shape.Fill>
<SolidColorBrush Color="{StaticResource TrackStaticBackgroundColor}"/>
</Shape.Fill>
</Rectangle>
<RepeatButton
x:Name="PART_LineDownButton"
Grid.Row="2"
Command="{x:Static ScrollBar.LineDownCommand}"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Opacity="0"
Width="{StaticResource ScrollBarCollapsedWidth}"
Style="{StaticResource LineButton}"
>
<Path
x:Name="ArrowDown"
Data="{Binding Source={StaticResource ArrowDownGeometry}}"
Fill="{Binding Parent.Foreground, RelativeSource={RelativeSource Self}}"
/>
</RepeatButton>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Thumb" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="VerticalScrollBar" TargetType="c:AutoHideScrollBar">
<Setter Property="Template" Value="{StaticResource VerticalScrollBarTemplate}"/>
</Style>
<Style TargetType="ScrollViewer" BasedOn="{StaticResource {x:Type ScrollViewer}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Grid>
<ScrollContentPresenter
x:Name="PART_ScrollContentPresenter"
CanContentScroll="{TemplateBinding CanContentScroll}"
>
<ScrollContentPresenter.Clip>
<MultiBinding Converter="{x:Static c:AutoHideScrollBar.ScrollContentClipConverter}">
<Binding ElementName="PART_VerticalScrollBar" Path="VisibleWidth" Mode="OneWay"/>
<Binding ElementName="PART_HorizontalScrollBar" Path="VisibleHeight" Mode="OneWay"/>
<Binding RelativeSource="{RelativeSource Self}" Path="ActualWidth" Mode="OneWay"/>
<Binding RelativeSource="{RelativeSource Self}" Path="ActualHeight" Mode="OneWay"/>
</MultiBinding>
</ScrollContentPresenter.Clip>
</ScrollContentPresenter>
<c:AutoHideScrollBar
x:Name="PART_VerticalScrollBar"
Cursor="{TemplateBinding Cursor}"
HorizontalAlignment="Right"
HorizontalContentAlignment="Right"
Maximum="{TemplateBinding ScrollableHeight}"
Value="{TemplateBinding VerticalOffset}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Template="{StaticResource VerticalScrollBarTemplate}"
>
</c:AutoHideScrollBar>
<c:AutoHideScrollBar
x:Name="PART_HorizontalScrollBar"
Cursor="{TemplateBinding Cursor}"
Maximum="{TemplateBinding ScrollableWidth}"
Orientation="Horizontal"
Value="{TemplateBinding HorizontalOffset}"
VerticalAlignment="Bottom"
VerticalContentAlignment="Bottom"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Template="{StaticResource HorizontalScrollBarTemplate}"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -1,80 +0,0 @@
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class AutoHideScrollbarResources : ResourceDictionary {
public AutoHideScrollbarResources() =>
InitializeComponent();
}
class AutoHideScrollBar : ScrollBar {
IDisposable _hooks;
public static IMultiValueConverter ScrollContentClipConverter =
ValueConverter.create<double, double, double, double, RectangleGeometry>(
(scroll_bar_width, scroll_bar_height, content_width, content_height) =>
new RectangleGeometry(
new Rect(new Size(
Math.Max(0, content_width - DoubleHelper.CoerceNaN(scroll_bar_width, 0)),
Math.Max(0, content_height - DoubleHelper.CoerceNaN(scroll_bar_height, 0))))));
#region VisibleWidth
static readonly DependencyPropertyKey VisibleWidthPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
nameof(VisibleWidth),
typeof(double),
typeof(AutoHideScrollBar),
new PropertyMetadata(double.NaN, null, CoerceVisibleWidth));
static object CoerceVisibleWidth(DependencyObject d, object baseValue) =>
(d as AutoHideScrollBar)?.Track?.ActualWidth ?? baseValue;
public static readonly DependencyProperty VisibleWidthProperty =
VisibleWidthPropertyKey.DependencyProperty;
public double VisibleWidth {
get => (double)GetValue(VisibleWidthProperty);
private set => SetValue(VisibleWidthPropertyKey, value);
}
#endregion
#region VisibleHeight
static readonly DependencyPropertyKey VisibleHeightPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
nameof(VisibleHeight),
typeof(double),
typeof(AutoHideScrollBar),
new PropertyMetadata(double.NaN, null, CoerceVisibleHeight));
static object CoerceVisibleHeight(DependencyObject d, object baseValue) =>
(d as AutoHideScrollBar)?.Track?.ActualHeight ?? baseValue;
public static readonly DependencyProperty VisibleHeightProperty =
VisibleHeightPropertyKey.DependencyProperty;
public double VisibleHeight {
get => (double)GetValue(VisibleHeightProperty);
private set => SetValue(VisibleHeightPropertyKey, value);
}
#endregion
public override void OnApplyTemplate() {
Disposable.dispose(ref _hooks);
/// call base method first to ensure <see cref="ScrollBar.Track"/> is initialized.
base.OnApplyTemplate();
_hooks = Track.SubscribeSizeChanged((s, e) => {
if (e.WidthChanged)
CoerceValue(VisibleWidthProperty);
if (e.HeightChanged)
CoerceValue(VisibleHeightProperty);
});
CoerceValue(VisibleWidthProperty);
CoerceValue(VisibleHeightProperty);
}
}
}
@@ -1,5 +1,4 @@
using mbts.ui.dpi;
using mbts.ui.converters;
using mbts.ui.converters;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System;
@@ -11,7 +10,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using bts;
namespace mbts.ui.controls {
partial class DIYGrid {
@@ -85,39 +83,79 @@ namespace mbts.ui.controls {
}
}
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
if (content_width > width || unclipped_left < left)
return new Rect(left, 0, width - left + unclipped_left, row_height);
return null;
}
public static IDisposable draw_with_clip(DrawingContext dc, double raw_content_width, double raw_content_height, Thickness padding, double row_height, ColumnGeometry position) =>
draw_with_clip(dc,
raw_content_width + padding.Left + padding.Right,
raw_content_height + padding.Top + padding.Bottom,
row_height, position.left, position.unclipped_left, position.width);
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
if (clip.HasValue) {
dc.PushClip(new RectangleGeometry(clip.Value));
return Disposable.Create(dc.Pop);
} else return Disposable.Create(() => { });
}
public static void draw_text(
DrawingContext dc,
GlyphContext glyph_context,
double font_size,
string text,
ColumnGeometry position,
double row_height,
Brush foreground,
Brush background,
HorizontalAlignment x_align,
VerticalAlignment y_align,
Thickness padding,
bool round_to_layout = true) {
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
//
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
if (!string.IsNullOrEmpty(text)) {
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
}
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
if (glyph_run == null && background == null)
return;
var rect = position.to_rect(row_height);
if (background != null) {
var guidelines = rect.AntiAliasGuidelines(0);
dc.PushGuidelineSet(guidelines);
dc.DrawRectangle(background, null, rect);
dc.Pop();
}
if (glyph_run != null) {
dc.PushClip(new RectangleGeometry(rect));
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
dc.DrawGlyphRun(foreground, glyph_run);
dc.Pop();
}
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
//
using (position.clip_draw(dc, row_height)) {
double adjusted_left = position.unclipped_left;
double available_width = position.width;
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
if (trigger_actions != null) {
foreach (CellAnimation ta in trigger_actions) {
switch (ta.animation_type_case) {
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
break;
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
break;
}
}
}
foreach (var (brush, glyph) in glyphs) {
dc.DrawGlyphRun(brush, glyph);
}
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
}
@@ -127,6 +165,14 @@ namespace mbts.ui.controls {
public static void change_position(FrameworkElement cell_view, double row_height, ColumnGeometry? position) {
if (position.HasValue) {
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Visible);
var clip = get_clip(cell_view.DesiredSize, row_height, 0, position.Value.unclipped_left - position.Value.left, position.Value.width);
var el_clip = cell_view.Clip as RectangleGeometry;
if (clip.HasValue) {
if (el_clip == null || !el_clip.Rect.Equals(clip.Value))
cell_view.Clip = new RectangleGeometry(clip.Value);
} else if (cell_view.Clip != null) {
cell_view.Clip = null;
}
} else {
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Collapsed);
}
@@ -1,25 +1,16 @@
using mbts.spark;
using mbts.ui.dpi;
using mbts.ui.utilities;
using mbts.ui.utilities;
using mbts.ui.utility.text;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
enum HyperlinkCellClickMode {
Text,
Anywhere
}
class HyperlinkCell : FrameworkElement, ICommandSource {
// these fields each represent properties that are accessed frequently,
// they are stored locally to reduce repetative DependencyProperty lookups
@@ -27,35 +18,10 @@ namespace mbts.ui.controls {
Brush _background, _foreground_backing, _border_brush_backing;
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
GlyphRun _glyph_run => _gcr.glyph_run;
bool
_is_mouse_over_text,
_is_mouse_over_click_area_backing,
_can_execute,
_snaps_to_device_pixels;
bool _is_mouse_over_text, _can_execute;
Pen _underline_pen_backing, _border_stroke_pen_backing;
GlyphContentRect _gcr = new GlyphContentRect {
// add 2 pixels to account for underline (1 for space, 1 for the line itself)
content_height_offset = HighDpiExtensions.pixel_unit * 2
};
IDisposable _mouse_over_click_area_op;
GlyphContentRect _gcr = new GlyphContentRect();
bool _is_mouse_over_click_area {
get => _is_mouse_over_click_area_backing;
set {
if (_is_mouse_over_click_area_backing != value) {
Disposable.dispose(ref _mouse_over_click_area_op);
_is_mouse_over_click_area_backing = value;
if (value) {
_mouse_over_click_area_op = WorkLoop.Start(
$"Evaluate {nameof(HyperlinkCell)} {nameof(IsMouseOverClickArea)}",
TimeSpan.FromMilliseconds(100),
DispatcherPriority.Input,
_on_mouse_changed,
WorkLoop.default_excessive_cycle_ratio);
}
}
}
}
Brush _foreground {
get => _foreground_backing;
set {
@@ -71,23 +37,13 @@ namespace mbts.ui.controls {
}
}
Thickness _border_thickness {
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
get => _gcr.border_thickness;
set {
_gcr.border_thickness = new Thickness(value.Left);
_border_stroke_pen_backing = null;
}
}
Pen _underline_pen {
get {
if (_underline_pen_backing == null) {
var fg = _foreground;
if (fg != null) {
_underline_pen_backing = new Pen(fg, _snaps_to_device_pixels ? HighDpiExtensions.pixel_unit : 1);
}
}
return _underline_pen_backing;
}
}
Pen _underline_pen => _underline_pen_backing ?? (_foreground == null ? null : (_underline_pen_backing = new Pen(_foreground, 1)));
Pen _border_stroke_pen =>
// border_thickness.Left is used as the uniform border thickness. This can be changed if need be.
_border_stroke_pen_backing == null && _border_brush != null && _border_thickness.Left > 0 ?
@@ -95,9 +51,6 @@ namespace mbts.ui.controls {
_border_stroke_pen_backing;
static HyperlinkCell() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkCell), new FrameworkPropertyMetadata(typeof(HyperlinkCell)));
IsMouseOverClickAreaProperty = IsMouseOverClickAreaPropertyKey.DependencyProperty;
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkCell), new FrameworkPropertyMetadata(typeof(HyperlinkCell)));
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
HorizontalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
@@ -111,11 +64,6 @@ namespace mbts.ui.controls {
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.width = v,
double.NaN);
WPFHelper.override_metadata<bool, HyperlinkCell>(
SnapsToDevicePixelsProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._snaps_to_device_pixels = v,
true);
}
public HyperlinkCell() {
@@ -125,7 +73,7 @@ namespace mbts.ui.controls {
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.padding = _underline_padding(Padding);
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
@@ -138,39 +86,8 @@ namespace mbts.ui.controls {
if (Text is string str && !string.IsNullOrEmpty(str))
_gcr.text_lines = new[] { str };
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
_snaps_to_device_pixels = SnapsToDevicePixels;
}
#region IsMouseOverClickArea
static readonly DependencyPropertyKey IsMouseOverClickAreaPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsMouseOverClickArea",
typeof(bool),
typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender,
OnIsMouseOverClickAreaChanged,
CoerceIsMouseOverClickArea));
public static readonly DependencyProperty IsMouseOverClickAreaProperty;
static void OnIsMouseOverClickAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is HyperlinkCell hlc)
hlc._is_mouse_over_click_area = e.NewValue is bool b && b;
}
static object CoerceIsMouseOverClickArea(DependencyObject d, object baseValue) =>
d is HyperlinkCell hlc ?
hlc._is_mouse_over_text || (hlc.IsMouseOver && hlc.HyperlinkClickMode == HyperlinkCellClickMode.Anywhere) :
baseValue;
public bool IsMouseOverClickArea {
get => (bool)GetValue(IsMouseOverClickAreaProperty);
private set => SetValue(IsMouseOverClickAreaPropertyKey, value);
}
#endregion
#region BorderThickness
public static readonly DependencyProperty BorderThicknessProperty =
Control.BorderThicknessProperty.AddOwner(typeof(HyperlinkCell),
@@ -205,7 +122,6 @@ namespace mbts.ui.controls {
Control.HorizontalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
HorizontalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((HyperlinkCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
@@ -219,8 +135,7 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
VerticalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((HyperlinkCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
public VerticalAlignment VerticalContentAlignment {
@@ -300,8 +215,7 @@ namespace mbts.ui.controls {
nameof(Padding),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(p, v) => p._gcr.padding = v,
DIYGrid.DefaultCellPadding);
(p, v) => p._gcr.padding = _underline_padding(v), DIYGrid.DefaultCellPadding);
public Thickness Padding { get => (Thickness)GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); }
#endregion
@@ -317,19 +231,6 @@ namespace mbts.ui.controls {
public string Text { get => GetValue(TextProperty) as string; set => SetValue(TextProperty, value); }
#endregion
#region HyperlinkClickMode
public static readonly DependencyProperty HyperlinkClickModeProperty =
WPFHelper.create_dp<HyperlinkCellClickMode, HyperlinkCell>(
nameof(HyperlinkClickMode),
(o, v) => o.CoerceValue(IsMouseOverClickAreaProperty),
HyperlinkCellClickMode.Anywhere);
public HyperlinkCellClickMode HyperlinkClickMode {
get => (HyperlinkCellClickMode)GetValue(HyperlinkClickModeProperty);
set => SetValue(HyperlinkClickModeProperty, value);
}
#endregion
#region ICommandSource
#region Command
@@ -396,8 +297,8 @@ namespace mbts.ui.controls {
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
_on_mouse_changed(e);
if (_is_mouse_over_click_area) {
_check_mouse_over_text(e);
if (_is_mouse_over_text) {
// Hyperlink should take focus when left mouse button is clicked on it
// This is consistent with all ButtonBase controls and current Win32 behavior
Focus();
@@ -407,8 +308,6 @@ namespace mbts.ui.controls {
if (e.ButtonState == MouseButtonState.Pressed) {
// Capture the mouse, and make sure we got it.
CaptureMouse();
} else {
ReleaseMouseCapture();
}
e.Handled = true;
}
@@ -416,25 +315,23 @@ namespace mbts.ui.controls {
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
base.OnMouseLeftButtonUp(e);
_on_mouse_changed(e);
if (IsMouseCaptured) {
_check_mouse_over_text(e);
if (IsMouseCaptured)
ReleaseMouseCapture();
if (_can_execute)
CommandHelper.ExecuteCommandSource(this);
_on_mouse_changed(e);
e.Handled = true;
}
if (_can_execute && _is_mouse_over_text)
CommandHelper.ExecuteCommandSource(this);
e.Handled = true;
}
#endregion
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) => _on_mouse_changed();
protected override void OnPreviewMouseMove(MouseEventArgs e) {
_on_mouse_changed(e);
e.Handled = IsMouseCaptured;
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) {
if (_is_mouse_over_text != IsMouseDirectlyOver)
_toggle_mouse_over_text();
}
protected override void OnMouseEnter(MouseEventArgs e) => _on_mouse_changed(e);
protected override void OnMouseLeave(MouseEventArgs e) => _on_mouse_changed(e);
protected override void OnPreviewMouseMove(MouseEventArgs e) => _check_mouse_over_text(e);
protected override void OnMouseEnter(MouseEventArgs e) => _check_mouse_over_text(e);
protected override void OnMouseLeave(MouseEventArgs e) => _check_mouse_over_text(e);
protected override Size ArrangeOverride(Size finalSize) => finalSize;
@@ -450,32 +347,45 @@ namespace mbts.ui.controls {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
var gr = _glyph_run;
if (gr != null) {
if (bg != null) {
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
}
if (_glyph_run != null) {
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, gr);
if (IsMouseOverClickArea) {
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
if (_is_mouse_over_text) {
// draw underline
var ul_pen = _underline_pen;
if (ul_pen != null) {
var line_start = new Point(gr.BaselineOrigin.X, gr.BaselineOrigin.Y + 2 * ul_pen.Thickness);
drawingContext.DrawHorizontalLine(ul_pen, line_start, gr.AdvanceWidths.Sum(), _snaps_to_device_pixels);
}
double x = _gcr.glyph_run.BaselineOrigin.X;
double y = _gcr.glyph_run.BaselineOrigin.Y + 2;
drawingContext.DrawLine(_underline_pen, new Point(x, y), new Point(x + _gcr.glyph_run.AdvanceWidths.Sum(), y));
}
drawingContext.Pop();
}
}
void _on_mouse_changed() =>
_on_mouse_changed(Mouse.GetPosition(this));
static Thickness _underline_padding(Thickness padding) =>
new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + 2);
void _on_mouse_changed(MouseEventArgs e) =>
_on_mouse_changed(e.GetPosition(this));
void _check_mouse_over_text(MouseEventArgs e) {
var pt = e.GetPosition(this);
var orig = _gcr.content_area;
var exp = orig.Expanded(1);
if (_is_mouse_over_text != exp.Contains(pt)) {
bts.Log.assert(_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)));
_toggle_mouse_over_text();
return;
}
if (_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)))
_toggle_mouse_over_text();
}
void _on_mouse_changed(Point pt) {
_is_mouse_over_text = _gcr.content_area.Expanded(1).Contains(pt);
CoerceValue(IsMouseOverClickAreaProperty);
void _toggle_mouse_over_text() {
_is_mouse_over_text = !_is_mouse_over_text;
if (_is_mouse_over_text)
SetCurrentValue(CursorProperty, Cursors.Hand);
else
ClearValue(CursorProperty);
InvalidateVisual();
}
}
}
@@ -1,5 +1,5 @@
using mbts.ui.dpi;
using mbts.ui.utility.text;
using mbts.ui.utility.text;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -15,7 +15,6 @@ namespace mbts.ui.controls {
Brush _background, _foreground, _border_brush_backing;
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
GlyphRun _glyph_run => _gcr.glyph_run;
bool _snaps_to_device_pixels;
Pen _border_stroke_pen_backing;
GlyphContentRect _gcr = new GlyphContentRect();
@@ -27,7 +26,7 @@ namespace mbts.ui.controls {
}
}
Thickness _border_thickness {
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
get => _gcr.border_thickness;
set {
_gcr.border_thickness = new Thickness(value.Left);
_border_stroke_pen_backing = null;
@@ -53,11 +52,6 @@ namespace mbts.ui.controls {
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.width = v,
double.NaN);
WPFHelper.override_metadata<bool, TextCell>(
SnapsToDevicePixelsProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._snaps_to_device_pixels = v,
true);
}
public TextCell() {
@@ -66,7 +60,7 @@ namespace mbts.ui.controls {
_gcr.max_width = MaxWidth;
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = MaxHeight;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
@@ -80,7 +74,6 @@ namespace mbts.ui.controls {
if (Text is string str && !string.IsNullOrEmpty(str))
_gcr.text_lines = new[] { str };
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
_snaps_to_device_pixels = SnapsToDevicePixels;
}
#region BorderThickness
@@ -117,9 +110,15 @@ namespace mbts.ui.controls {
Control.HorizontalContentAlignmentProperty.AddOwner(typeof(TextCell),
new FrameworkPropertyMetadata(
HorizontalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
(d, e) => {
if (e.OldValue is HorizontalAlignment oldha) {
if (oldha == HorizontalAlignment.Right) {
}
}
((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue;
}));
public HorizontalAlignment HorizontalContentAlignment {
get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
@@ -131,8 +130,7 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(TextCell),
new FrameworkPropertyMetadata(
VerticalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((TextCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
public VerticalAlignment VerticalContentAlignment {
@@ -239,17 +237,35 @@ namespace mbts.ui.controls {
return _gcr.layout_size;
}
double bl_x = 0;
protected override void OnRender(DrawingContext drawingContext) {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
var gr = _glyph_run;
if (gr != null) {
if (bg != null) {
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
}
if (_glyph_run != null) {
var new_x = _glyph_run.BaselineOrigin.X;
if (Math.Abs(bl_x - new_x) > 5) {
if (new_x < bl_x) {
}
}
if (Text == "-3,375.000") {
}
bl_x = new_x;
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, gr);
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
drawingContext.Pop();
}
if (bg != null) {
var bgo = DIYGrid.GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
}
}
}
}
}
@@ -1,76 +1,30 @@
using bts.utils;
using System;
using System.Collections.Generic;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.controls {
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public readonly struct CellGeometry {
public CellGeometry(ColumnGeometry column, ListBoxItem row) {
this.column = column;
this.row = row;
}
public ColumnGeometry column { get; }
public ListBoxItem row { get; }
public void Deconstruct(out ListBoxItem row, out ColumnGeometry column) {
row = this.row;
column = this.column;
}
public Rect to_rect(double y) =>
column.to_visible_rect(row.ActualHeight, y);
public static implicit operator (ListBoxItem, ColumnGeometry) (CellGeometry cell) =>
(cell.row, cell.column);
}
public readonly struct ColumnGeometry : IComparable<ColumnGeometry> {
public static readonly IEqualityComparer<ColumnGeometry> PositionEqualityComparer =
EqualityComparer.Create<ColumnGeometry, (double, double)>(c => (c.left, c.right));
public readonly ColumnBase column;
public readonly DIYGridSuperHeader super_column;
public readonly double
left,
// If the column was unclipped
unclipped_left,
right;
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
: this(column, super_column, left, left, right) { }
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
this.column = column;
this.super_column = super_column;
this.left = left;
this.unclipped_left = unclipped_left;
this.right = right;
}
public double width => right - left;
public struct ColumnGeometry {
public ColumnBase column;
public DIYGridSuperHeader super_column;
public double unclipped_left; /* If the column was unclipped */
public double left;
public double width { get { return column.ActualWidth; } }
public double right { get { return unclipped_left + width; } }
public bool is_partially_obscured { get { return unclipped_left < left; } }
public double visible_width => Math.Max(0, width - left + unclipped_left);
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public Rect to_rect(double row_height) =>
new Rect(left, 0, width, row_height);
public Rect to_visible_rect(double row_height, double y) =>
new Rect(unclipped_left, y, visible_width, row_height);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public int CompareTo(ColumnGeometry other) {
int comp = left.CompareTo(other.left);
if (comp != 0)
return comp;
return right.CompareTo(other.right);
public RectangleGeometry get_clip(double row_height) =>
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
public IDisposable clip_draw(DrawingContext dc, double row_height) {
dc.PushClip(get_clip(row_height));
return Disposable.Create(dc.Pop);
}
}
}
@@ -1,108 +0,0 @@
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace mbts.ui.controls {
class ColumnSortManager {
readonly List<DIYGrid.ColumnBase> _visible_sorting = new List<DIYGrid.ColumnBase>();
readonly IList<DIYGrid.ColumnBase> _columns;
readonly string _primary_key;
public ColumnSortManager(IList<DIYGrid.ColumnBase> columns, string primary_key) {
_columns = columns;
_primary_key = primary_key;
}
public IEnumerable<DIYGrid.ColumnBase> visible_sorting =>
_visible_sorting.AsEnumerable();
public IEnumerable<DIYGrid.ColumnBase> active_sorting {
get {
bool has_primary = false;
foreach (var col in _visible_sorting) {
if (!has_primary && col.id == _primary_key) {
has_primary = true;
}
yield return col;
}
if (!has_primary) {
var pcol = _find_column(_primary_key);
if (pcol != null)
yield return pcol;
}
}
}
public IEnumerable<SortingCriteria> create() {
foreach (var col in _visible_sorting) {
var dir = col.SortDirection;
if (dir.HasValue)
yield return new SortingCriteria(col.id, dir.Value == ListSortDirection.Ascending);
}
}
public void load(IEnumerable<SortingCriteria> sorting_criterion) {
_clear();
foreach (var sc in sorting_criterion) {
var col = _find_column(sc.col_name);
if (col != null) {
col.SortDirection = sc.direction();
_visible_sorting.Add(col);
}
}
}
public void on_column_click(string id, bool is_shift_pressed) =>
on_column_click(_find_column(id), is_shift_pressed);
public void on_column_click(DIYGrid.ColumnBase col, bool is_shift_pressed) {
if (col == null)
return;
var old_dir = col.SortDirection;
if (!is_shift_pressed) {
_clear();
col.SortDirection = _reverse(old_dir);
_visible_sorting.Add(col);
} else {
col.SortDirection = _reverse(old_dir);
if (!old_dir.HasValue)
_visible_sorting.Add(col);
}
}
static ListSortDirection _reverse(ListSortDirection? dir) =>
dir.HasValue && dir.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
void _clear() {
_visible_sorting.Clear();
foreach (var c in _columns)
c.SortDirection = null;
}
DIYGrid.ColumnBase _find_column(string id) =>
_columns.FirstOrDefault(c => c.id == id);
}
static class ColumnSortManagerExtensions {
public static IComparer<TRow> build_comparer<TColumnKey, TRow>(
this ColumnSortManager sort_manager,
Func<TColumnKey, IComparer<TRow>> get_column_comparer)
where TColumnKey : struct {
//
var bd = new ComparerBuilder<TRow>();
foreach (var col in sort_manager.active_sorting) {
if (Enum.TryParse(col.id, true, out TColumnKey key)) {
var dir = col.SortDirection;
if (dir.HasValue) {
var comp = get_column_comparer(key);
if (comp != null)
bd.add(DIYGridSortHelper.reverse_if_descending(comp, dir.Value));
}
}
}
return bd.to_comparer();
}
}
}
@@ -1,65 +1,40 @@
using mbts.spark.filters;
using mbts.ui.converters;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
internal class HyperlinkElementColumn<TKey> : ColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
public HyperlinkElementColumn(TKey key, IDataGridColumnFilter filter, string filter_template)
: base(key) {
internal class HyperlinkElementColumn<TRowVM, TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
readonly BindingBase _binding, _command_binding;
public HyperlinkElementColumn(
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
this.filter = filter;
this.filter_template = filter_template;
_binding = binding;
_command_binding = command_binding;
}
public event Action<HyperlinkCell> view_generated;
public Style ElementStyle { get; set; }
public IDataGridColumnFilter filter { get; set; }
public string filter_template { get; set; }
// bindings
public BindingBase CommandBinding { get; set; }
public BindingBase CommandParameterBinding { get; set; }
public BindingBase TextBinding { get; set; }
public FrameworkElement view_factory() {
var hl = new HyperlinkCell();
hl.SetCurrentValue(DIYGrid.ColumnProperty, this);
// bindings
if (TextBinding != null)
hl.SetBinding(HyperlinkCell.TextProperty, TextBinding);
if (CommandBinding != null)
hl.SetBinding(HyperlinkCell.CommandProperty, CommandBinding);
if (CommandParameterBinding != null)
hl.SetBinding(HyperlinkCell.CommandParameterProperty, CommandParameterBinding);
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
public override FrameworkElement view_factory() {
var hl = new HyperlinkCell { Padding = padding };
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
hl.SetBinding(
CursorProperty,
new Binding {
Source = hl,
Path = new PropertyPath(HyperlinkCell.IsMouseOverClickAreaProperty),
Converter = new BooleanToObjectConverter {
ValueForFalse = Cursors.Arrow,
ValueForTrue = Cursors.Hand
}
});
// background
var bg_brush = new SolidColorBrush();
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
hl.SetCurrentValue(HyperlinkCell.BackgroundProperty, bg_brush);
// style
if (ElementStyle != null)
hl.Style = ElementStyle;
view_generated?.Invoke(hl);
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
return hl;
}
}
}
}
}
@@ -1,50 +1,44 @@
using mbts.spark.filters;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
internal class TextColumn<TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
internal class TextColumn<TColKey> : FrameworkElementColumnBase<TColKey>, IFilterableColumn {
readonly BindingBase _binding;
public TextColumn(
TKey key, IDataGridColumnFilter filter, string filter_template) : base(key) {
public TextColumn(TColKey key, BindingBase binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
this.filter = filter;
this.filter_template = filter_template;
_binding = binding;
}
public Style ElementStyle { get; set; }
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
// bindings
public BindingBase TextBinding { get; set; }
public BindingBase ToolTipBinding { get; set; }
public BindingBase background_binding { get; set; }
public BindingBase font_weight_binding { get; set; }
public BindingBase foreground_binding { get; set; }
public BindingBase tooltip_binding { get; set; }
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
public override FrameworkElement view_factory() {
var v = new TextCell {
Padding = padding
};
// bindings
if (TextBinding != null)
v.SetBinding(TextCell.TextProperty, TextBinding);
if (ToolTipBinding != null)
v.SetBinding(ToolTipProperty, ToolTipBinding);
v.SetBinding(TextCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
var v = new TextCell { Foreground = font_brush, VerticalAlignment = vertical_alignment };
v.SetBinding(TextCell.TextProperty, _binding);
v.SetBinding(TextCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
v.SetBinding(TextCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
v.SetBinding(TextCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
v.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
// background
var bg_brush = new SolidColorBrush();
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
v.Background = bg_brush;
// style
if (ElementStyle != null)
v.Style = ElementStyle;
v.SetBinding(TextCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
v.SetBinding(TextCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
try_set_binding(TextCell.BackgroundProperty, background_binding);
try_set_binding(TextCell.ForegroundProperty, foreground_binding);
try_set_binding(TextCell.FontWeightProperty, font_weight_binding);
try_set_binding(TextCell.ToolTipProperty, tooltip_binding);
return v;
//
void try_set_binding(DependencyProperty p, BindingBase b) {
if (b != null)
v.SetBinding(p, b);
}
}
}
}
}
}
+106 -395
View File
@@ -1,11 +1,9 @@
using bts;
using bts.collections;
using bts.utils;
using mbts.spark.filters;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.settings;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -24,7 +22,6 @@ using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace mbts.ui.controls {
interface IHandleGridSorting {
@@ -77,26 +74,6 @@ namespace mbts.ui.controls {
}
}
public static void reset_sorting<TKey>(IEnumerable<DIYGrid.ColumnBase> columns, IEnumerable<SortingCriteria> sorting_criterion) where TKey : struct =>
reset_sorting(columns, convert_sorting_criterion<TKey>(sorting_criterion));
public static void reset_sorting<TKey>(IEnumerable<DIYGrid.ColumnBase> columns, IEnumerable<(TKey key, ListSortDirection direction)> sorting_hierarchy) where TKey : struct {
// TODO: incorporate the sort hierarchy/index, for nested sorting
var active_cols = sorting_hierarchy.empty_if_null()
.Select((k, i) => (k, i))
.ToDictionary(tup => tup.k.key, tup => (tup.k.direction, tup.i));
// columns are dispatcher objects, and might throw if they are accessed after the view has been shut down
try {
foreach (var c in columns) {
if (Enum.TryParse(c.id, true, out TKey key) && active_cols.TryGetValue(key, out var tup)) {
c.SortDirection = tup.direction;
} else {
c.SortDirection = null;
}
}
} catch { }
}
public static IObservable<ImmutableList<(TKey key, IComparer<TRow> comparer, ListSortDirection direction)>> observe_active_comparers<TKey, TRow>(
DIYGrid grid, Func<TKey, IComparer<TRow>> get_comparer, bool mark_handled = true) =>
//
@@ -225,39 +202,24 @@ namespace mbts.ui.controls {
}
class DIYGridRowsPresenter : ListBox {
static readonly PropertyPath
_highlight_path = new PropertyPath(DIYGrid.SelectionHighlightProperty),
_active_path = new PropertyPath(IsSelectionActiveProperty);
static readonly IValueConverter
_active_opacity_converter = ValueConverter.create<bool, double>(is_active => is_active ? 1.0 : 0.5);
readonly LoggerContext _log = new LoggerContext(Log.Ui, $"{nameof(DIYGridRowsPresenter)} ", null);
double _row_height = DIYGrid.DefaultRowHeight;
ScrollContentPresenter _content_presenter_backing;
DIYGrid _grid;
AdornerHost<Rectangle> _drag_rect_adorner;
DragRectAdorner _drag_rect_adorner;
RowSelectionAdorner _rsa;
DIYGridCellHoverAdorner _cell_hover_adorner;
ScrollViewer _scroll_viewer;
Point? _drag_start, _drag_stop;
double
_drag_start_horizontal_drift,
_drag_start_vertical_drift;
Point? _drag_start;
double _drag_start_vertical_drift;
bool _support_group_items;
int _drag_start_index;
object _drag_start_item;
IDisposable _drag_hook;
static DIYGridRowsPresenter() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGridRowsPresenter), new FrameworkPropertyMetadata(typeof(DIYGridRowsPresenter)));
public DIYGridRowsPresenter() {
Loaded += on_loaded;
Unloaded += on_unloaded;
//
void on_loaded(object sender, RoutedEventArgs e) {
destroy_state();
Loaded += (s, e) => {
if (_grid != null)
_grid.RowHeightChanged -= on_row_height_changed;
_grid = this.find_ancestor_of_type<DIYGrid>();
@@ -266,61 +228,25 @@ namespace mbts.ui.controls {
_support_group_items = _grid.SupportGroupItems;
_grid.SupportGroupItemsChanged += on_support_group_items_changed;
_scroll_viewer = this.descendants_of_type<ScrollViewer>().First();
// selection highlighting
if (SelectionMode != SelectionMode.Single) {
_drag_rect_adorner = new DragRectAdorner(this, _scroll_viewer, _row_height);
_drag_rect_adorner.SetResourceReference(DragRectAdorner.FillProperty, DIYGrid.ActiveSelectionHighlightKey);
_drag_rect_adorner.SetResourceReference(DragRectAdorner.StrokeProperty, DIYGrid.SelectionStrokeKey);
_drag_rect_adorner.attach();
}
_rsa = new RowSelectionAdorner(this, _scroll_viewer);
_rsa.bind(RowSelectionAdorner.FillProperty, _highlight_path, DIYGrid.SelectionHighlightBrushConverter, _grid);
_rsa.bind(RowSelectionAdorner.StrokeProperty, _highlight_path, DIYGrid.SelectionHighlightPenConverter, _grid);
_rsa.bind(OpacityProperty, _active_path, DIYGrid.SelectionHighlightOpacityConverter, this);
_rsa.attach();
// cell hover adorner
_cell_hover_adorner = new DIYGridCellHoverAdorner(this, _scroll_viewer);
_cell_hover_adorner.SetBinding(VisibilityProperty, new Binding {
Path = new PropertyPath(IsMouseOverProperty),
Converter = new converters.BooleanToVisibilityConverter(),
Source = this
});
// drag
_drag_hook = Disposable.Create(
DragDropHelper.subscribe_drag_coordinates(this, init_element: _content_presenter, use_preview_events: true)
.Subscribe(pt => {
if (_drag_start.HasValue) {
if (pt.HasValue) {
_drag_stop = pt.Value;
_refresh_drag_rect();
} else {
if (_drag_stop.HasValue) {
_end_drag(_drag_stop.Value);
}
_drag_start = null;
_drag_stop = null;
}
} else if (pt.HasValue) {
_begin_drag_selection(pt.Value);
}
}),
_scroll_viewer.SubscribeScrollChanged((s, _) => _refresh_drag_rect()));
}
void on_unloaded(object sender, RoutedEventArgs e) =>
destroy_state();
// idempotent
void destroy_state() {
Disposable.dispose(ref _drag_hook);
};
Unloaded += (s, e) => {
if (_drag_rect_adorner != null) {
_drag_rect_adorner.detach();
_drag_rect_adorner = null;
Disposable.dispose(ref _drag_rect_adorner);
}
if (_rsa != null) {
_rsa.detach();
Disposable.dispose(ref _rsa);
}
if (_cell_hover_adorner != null) {
_cell_hover_adorner.detach();
Disposable.dispose(ref _cell_hover_adorner);
}
}
};
void on_row_height_changed(double v) => _row_height = v;
void on_support_group_items_changed(bool v) => _support_group_items = v;
}
@@ -338,8 +264,8 @@ namespace mbts.ui.controls {
base.UnselectAll();
}
void _begin_drag_selection(Point pt) {
_drag_rect_adorner?.detach();
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
var pt = e.GetPosition(this);
if (InputHitTest(pt) is UIElement el) {
var sb = new StringBuilderEx("beginning drag selection on ");
OneOf.MatchFirst<DependencyObject, ListBoxItem, GroupItem>(el.VisualAncestors().Prepend(el)).Match(init_common, on_group);
@@ -353,43 +279,22 @@ namespace mbts.ui.controls {
}
}
void init_common(ListBoxItem item_container) {
// drag state
_drag_start = pt;
_drag_stop = pt;
_drag_start_item = ItemContainerGenerator.ItemFromContainer(item_container);
_drag_start_index = ItemContainerGenerator.IndexFromContainer(item_container);
_drag_start_horizontal_drift = _scroll_viewer.HorizontalOffset;
_drag_start_vertical_drift = _scroll_viewer.VerticalOffset;
if (!IsKeyboardFocusWithin)
Keyboard.Focus(item_container);
// drag rect
_drag_rect_adorner = new AdornerHost<Rectangle>(_content_presenter ?? (UIElement)this) {
Child = new Rectangle {
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
StrokeThickness = HighDpiExtensions.pixel_unit
},
IsClipEnabled = true
};
_drag_rect_adorner.Child.bind(Shape.FillProperty, _highlight_path, DIYGrid.SelectionHighlightBrushConverter, _grid);
_drag_rect_adorner.Child.bind(Shape.StrokeProperty, _highlight_path, DIYGrid.SelectionHighlightOpaqueBrushConverter, _grid);
_refresh_drag_rect();
_drag_rect_adorner.attach();
// log
sb.Add($"'{_drag_start_item}' (selection mode {SelectionMode}).");
_log.info(sb.ToString());
}
}
base.OnPreviewMouseLeftButtonDown(e);
}
void _end_drag(Point drag_stop) {
if (_drag_rect_adorner != null) {
_drag_rect_adorner.detach();
_drag_rect_adorner = null;
}
protected override void OnPreviewMouseUp(MouseButtonEventArgs e) {
if (_drag_start.HasValue) {
_log.info("ending drag selection on mouse up.");
if (SelectionMode != SelectionMode.Single && _content_presenter != null) {
var drag_stop = e.GetPosition(this);
drag_stop.X = MathUtils.within_range(0, _content_presenter.RenderSize.Width - 1, drag_stop.X);
drag_stop.Y = MathUtils.within_range(0, _content_presenter.RenderSize.Height - 1, drag_stop.Y);
double vertical_diff = drag_stop.Y + (_row_height * (_scroll_viewer.VerticalOffset - _drag_start_vertical_drift)) - _drag_start.Value.Y;
@@ -451,22 +356,7 @@ namespace mbts.ui.controls {
_drag_start = null;
_drag_start_item = null;
}
}
void _refresh_drag_rect() {
if (_drag_rect_adorner == null || !_drag_start.HasValue || !_drag_stop.HasValue)
return;
var hdiff = _scroll_viewer.HorizontalOffset - _drag_start_horizontal_drift;
var vdiff = _scroll_viewer.VerticalOffset - _drag_start_vertical_drift;
var rect = new Rect(
new Point(_drag_start.Value.X - hdiff, _drag_start.Value.Y - vdiff * _grid.RowHeight),
_drag_stop.Value);
if (rect.IsEmpty)
return;
rect.Intersect(this.GetChildBounds(_drag_rect_adorner.AdornedElement));
_drag_rect_adorner.Child.Margin = new Thickness(rect.Left, rect.Top, 0, 0);
_drag_rect_adorner.Child.Width = rect.Width;
_drag_rect_adorner.Child.Height = rect.Height;
base.OnPreviewMouseUp(e);
}
void _select_items_between(object inclusive_begin, object inclusive_end) {
@@ -488,6 +378,10 @@ namespace mbts.ui.controls {
}
partial class DIYGrid : Control {
public const string
ActiveSelectionHighlightKey = nameof(ActiveSelectionHighlightKey),
InactiveSelectionHighlightKey = nameof(InactiveSelectionHighlightKey),
SelectionStrokeKey = nameof(SelectionStrokeKey);
public const double
DefaultRowHeight = 17,
DefaultColumnHeaderHeight = 30;
@@ -517,47 +411,55 @@ namespace mbts.ui.controls {
// TODO: SelectionFill/Stroke should not be hardcoded, for now this serves as the central location for DragRectAdorner,
// and FrameworkElementColumns that paint the selection brush themselves
// (e.g MarketView bid/ask columns, bc their background is not transparent, it covers the ListBoxItem selection-highlight).
public static readonly Color DefaultSelectionHighlight = Colors.MediumSlateBlue.with(0x63);
public static readonly SolidColorBrush DefaultSelectionHighlight = new SolidColorBrush(Colors.MediumSlateBlue) { Opacity = 0.4 };
public static readonly Pen DefaultSelectionStroke = new Pen(Brushes.MediumSlateBlue, 1);
readonly NotifyCollectionChangedEventHandler _sort_descriptions_collection_changed_cb;
double _inactive_selection_opacity = 0.2;
DataContextHooks _data_context_hooks;
public static readonly DependencyProperty SelectionHighlightProperty =
WPFHelper.create_dp<Color, DIYGrid>(nameof(SelectionHighlight), DefaultSelectionHighlight);
WPFHelper.create_dp<SolidColorBrush, DIYGrid>(
nameof(SelectionHighlight),
(o, v) => {
o.Resources[ActiveSelectionHighlightKey] = v;
o._reset_inactive_selection_brush();
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_solid_brush().frozen(), 1).frozen();
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
col.on_selection_fill(v);
var ad_layer = System.Windows.Documents.AdornerLayer.GetAdornerLayer(o);
if (ad_layer != null) {
foreach (var ad in ad_layer.descendants_of_type<ISelectionHighlightListener>()) {
ad.on_selection_fill(v);
}
}
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
DefaultSelectionHighlight);
public Color SelectionHighlight {
get => (Color)GetValue(SelectionHighlightProperty);
public SolidColorBrush SelectionHighlight {
get => GetValue(SelectionHighlightProperty) as SolidColorBrush;
set => SetValue(SelectionHighlightProperty, value);
}
static DIYGrid() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(typeof(DIYGrid)));
UseLayoutRoundingProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(OnUseLayoutRoundingChanged));
void OnUseLayoutRoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
d.CoerceValue(RowHeightProperty);
DefaultSelectionHighlight.Freeze();
DefaultSelectionStroke.Freeze();
}
public DIYGrid() {
var sort_map = new Dictionary<string, ColumnBase>();
_sort_descriptions_collection_changed_cb = WPFHelper.create_collection_changed_cb<SortDescription>(
sd => {
if (!SyncColumnSortDirectionWithSortDescriptions)
return;
if (Columns.TryGetFirst(c => c.sort_member_path == sd.PropertyName, out ColumnBase col)) {
sort_map[sd.PropertyName] = col;
col.SortDirection = sd.Direction;
}
}, sd => {
if (!SyncColumnSortDirectionWithSortDescriptions)
return;
if (Columns.TryGetFirst(c => c.sort_member_path == sd.PropertyName, out ColumnBase col)) {
col.SortDirection = null;
sort_map.Remove(sd.PropertyName);
}
}, s => {
if (!SyncColumnSortDirectionWithSortDescriptions)
return;
sort_map.Clear();
if (Columns is IEnumerable<ColumnBase> cols) {
foreach (var c in cols)
@@ -603,6 +505,8 @@ namespace mbts.ui.controls {
}
}
}));
Resources[ActiveSelectionHighlightKey] = SelectionHighlight;
_reset_inactive_selection_brush();
_data_context_hooks = new DataContextHooks(this, DataContext);
DataContextChanged += OnDataContextChanged;
}
@@ -610,26 +514,15 @@ namespace mbts.ui.controls {
public event DIYGridSortingEventHandler Sorting;
public event Action<bool> SupportGroupItemsChanged;
public static IValueConverter SelectionHighlightPenConverter =>
ValueConverter.create<Color, Pen>(c => FrozenPenCache.get_pen(c.with(255)));
public static IValueConverter SelectionHighlightBrushConverter =>
ValueConverter.create<Color, Brush>(FrozenBrushCache.get_brush);
public static IValueConverter SelectionHighlightOpaqueBrushConverter =>
ValueConverter.create<Color, Brush>(c => FrozenBrushCache.get_brush(c.with(255)));
public static IValueConverter SelectionHighlightOpacityConverter =>
ValueConverter.create<bool, double>(is_active => is_active ? 1.0 : 0.5);
public static IValueConverter RowHoverHighlightBrushConverter =>
ValueConverter.create<Color, Brush>(c => FrozenBrushCache.get_brush(c, 0.35));
public static IValueConverter CellHoverHighlightPenConverter =>
ValueConverter.create<Color, Pen>(c => FrozenPenCache.get_pen(c.with(255)));
public static IValueConverter CellHoverHighlightBrushConverter =>
ValueConverter.create<Color, Brush>(c => FrozenBrushCache.get_brush(c, 0.5));
public double InactiveSelectionOpacity {
get => _inactive_selection_opacity;
set {
if (value != _inactive_selection_opacity && value.is_within_range(0.1, 0.9)) {
_inactive_selection_opacity = value;
_reset_inactive_selection_brush();
}
}
}
public bool SupportGroupItems {
get => _support_group_items;
@@ -641,8 +534,6 @@ namespace mbts.ui.controls {
}
}
public bool SyncColumnSortDirectionWithSortDescriptions { get; set; } = true;
public ItemContainerGenerator RowContainerGenerator =>
_row_presenter.ItemContainerGenerator;
@@ -691,6 +582,18 @@ namespace mbts.ui.controls {
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) =>
Disposable.replace(ref _data_context_hooks, new DataContextHooks(this, e.NewValue));
void _reset_inactive_selection_brush() {
try {
if (TryFindResource(ActiveSelectionHighlightKey) is SolidColorBrush brush) {
var clone = brush.Clone();
if (_inactive_selection_opacity < clone.Opacity) {
clone.Opacity = _inactive_selection_opacity;
}
Resources[InactiveSelectionHighlightKey] = clone;
}
} catch { }
}
#region Column Handling
public void invalidate_column_layout(object sender = null, EventArgs args = null) {
var geometry = GetColumnGeometry().ToList();
@@ -930,22 +833,6 @@ namespace mbts.ui.controls {
// Public Properties
#region Public Properties
#region Column
public static readonly DependencyProperty ColumnProperty =
DependencyProperty.RegisterAttached("Column", typeof(ColumnBase), typeof(DIYGrid));
public static ColumnBase GetColumn(DependencyObject d) => d.GetValue(ColumnProperty) as ColumnBase;
public static void SetColumn(DependencyObject d, ColumnBase value) => d.SetValue(ColumnProperty, value);
#endregion
#region Grid
public static readonly DependencyProperty GridProperty =
DependencyProperty.RegisterAttached("Grid", typeof(DIYGrid), typeof(DIYGrid));
public static DIYGrid GetGrid(DependencyObject d) => d.GetValue(GridProperty) as DIYGrid;
public static void SetGrid(DependencyObject d, DIYGrid value) => d.SetValue(GridProperty, value);
#endregion
#region ColumnKey
public static readonly DependencyProperty ColumnKeyProperty =
DependencyProperty.RegisterAttached("ColumnKey",
@@ -1182,7 +1069,7 @@ namespace mbts.ui.controls {
if (v < min_row_height)
return min_row_height;
}
return o.UseLayoutRounding ? v.RoundLayoutValue() : v;
return v;
}, DefaultRowHeight);
public event Action<double> RowHeightChanged;
@@ -1203,11 +1090,14 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty RowsProperty =
WPFHelper.create_dp<IEnumerable, DIYGrid>(
nameof(Rows),
(s, oldval, newval) =>
WPFHelper.manage_collection_change(
(oldval as ICollectionView)?.SortDescriptions,
(newval as ICollectionView)?.SortDescriptions,
s._sort_descriptions_collection_changed_cb),
(s, oldval, newval) => {
WPFHelper.manage_collection_change(
(oldval as ICollectionView)?.SortDescriptions,
(newval as ICollectionView)?.SortDescriptions,
s._sort_descriptions_collection_changed_cb,
old_ncc => s._sort_descriptions_collection_changed_cb(old_ncc, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)),
new_ncc => s._sort_descriptions_collection_changed_cb(new_ncc, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)new_ncc)));
},
Enumerable.Empty<object>());
public DataTemplate RowTemplate {
@@ -1256,30 +1146,33 @@ namespace mbts.ui.controls {
double frozen_offset = 0;
int vis_frozen_col_count = _get_visible_frozen_column_count();
foreach (var col in _visible_columns.Take(vis_frozen_col_count)) {
double right = frozen_offset + col.ActualWidth;
right = right.RoundLayoutValue();
yield return new ColumnGeometry(col, null, frozen_offset, frozen_offset, right);
yield return new ColumnGeometry() {
column = col,
left = frozen_offset,
unclipped_left = frozen_offset,
};
frozen_offset += col.ActualWidth;
}
if (_frozen_column_count > 0) {
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
}
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
// Adjust by scroll amount
double offset = frozen_offset - _scroll_horizontal.Value;
offset = offset.RoundLayoutValue();
double offset = frozen_offset - (_scroll_horizontal.Value);
int count = 0;
foreach (var col in _visible_columns.Skip(vis_frozen_col_count)) {
double left = Math.Max(offset, frozen_offset);
double right = offset + col.ActualWidth;
right = right.RoundLayoutValue();
// If right < frozen_offset column is completely obscured
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
// fully visible
yield return new ColumnGeometry(col, _superheader_column_index.ElementAtOrDefault(count), left, offset, right);
yield return new ColumnGeometry() {
column = col,
super_column = _superheader_column_index.ElementAtOrDefault(count),
unclipped_left = offset,
left = left
};
}
offset = right;
offset += col.ActualWidth;
++count;
}
}
@@ -1526,201 +1419,19 @@ namespace mbts.ui.controls {
public static IObservable<T> SubscribeMouseHoverRow<T>(this DIYGrid grid, IEqualityComparer<T> comparer) where T : class =>
Observable.Create<T>(o => {
T last_row = null;
return grid.SubscribeMouseHoverRowContainer().Subscribe(row_view => {
if (grid.RowContainerGenerator.ItemFromContainer(row_view) is T row && !comparer.Equals(row, last_row))
o.OnNext(last_row = row);
});
});
public static IObservable<ListBoxItem> SubscribeMouseHoverRowContainer(this DIYGrid grid) =>
Observable.Create<ListBoxItem>(o => {
bool busy = false, dirty = false;
ListBoxItem curr_row = null;
T curr_row = null;
return Disposable.Create(
grid.SubscribeMouseEnter(on_mouse),
grid.SubscribeMouseLeave(on_mouse),
grid.SubscribeMouseLeftButtonDown(on_mouse),
grid.SubscribeMouseLeftButtonUp(on_mouse),
grid.SubscribeMouseMove(on_mouse));
void on_mouse(object sender, MouseEventArgs e) => refresh();
grid.SubscribeMouseEnter(on_mouse_enter),
grid.SubscribeMouseLeave(on_mouse_leave),
grid.SubscribePreviewMouseMove(on_mouse_move));
void on_mouse_enter(object sender, MouseEventArgs e) => refresh();
void on_mouse_move(object sender, MouseEventArgs e) => refresh();
void on_mouse_leave(object sender, MouseEventArgs e) => refresh();
void refresh() {
if (busy) {
dirty = true;
} else {
dirty = false;
busy = true;
do {
grid.Dispatcher.InvokeAsync(() => {
var row = grid.ChildAtPoint<ListBoxItem>(Mouse.GetPosition(grid));
if (row != curr_row)
o.OnNext(curr_row = row);
dirty = false;
});
} while (dirty);
busy = false;
}
T row = grid.ChildAtPoint<ListBoxItem>(Mouse.GetPosition(grid)) is var rview && rview != null ? grid.RowContainerGenerator.ItemFromContainer(rview) as T : null;
if (!comparer.Equals(curr_row, row))
o.OnNext(curr_row = row);
}
});
public static IObservable<DIYGrid.CellGeometry?> SubscribeMouseHoverCell(this DIYGrid grid) {
return Observable.Create<DIYGrid.CellGeometry?>(o => {
IDisposable row_mouse_hooks = null;
DIYGrid.CellGeometry? last_value = null;
var layout = grid.GetColumnGeometry().ToList();
var layout_x_pts = layout.WrapSelector(cg => cg.left, null);
ListBoxItem hover_row = null;
grid.ColumnLayoutChanged += on_layout;
var hover_sub = grid.SubscribeMouseHoverRowContainer().Subscribe(on_row_view);
return Disposable.Create(dispose);
//
void notify(MouseEventArgs e = null) {
if (hover_row == null || layout.Count == 0)
on_cell(null);
else {
var pt = e?.GetPosition(hover_row) ?? Mouse.GetPosition(hover_row);
int index = CollectionHelper.binary_search(layout_x_pts, pt.X);
if (index < 0)
index = ~index - 1;
if (index >= 0 && index < layout.Count) {
on_cell(new DIYGrid.CellGeometry(layout[index], hover_row));
} else {
on_cell(null);
}
}
}
void on_cell(DIYGrid.CellGeometry? cell) {
if (!cell.Equals(last_value))
o.OnNext(last_value = cell);
}
void on_row_view(ListBoxItem row_view) {
row_mouse_hooks?.Dispose();
hover_row = row_view;
row_mouse_hooks = row_view?.SubscribeMouseMove(on_mouse_change);
notify();
}
void on_layout(List<DIYGrid.ColumnGeometry> obj) {
layout = grid.GetColumnGeometry().ToList();
layout_x_pts = layout.WrapSelector(cg => cg.left, null);
notify();
}
void on_mouse_change(object sender, MouseEventArgs e) =>
notify(e);
void dispose() {
Disposable.dispose(ref hover_sub);
grid.ColumnLayoutChanged -= on_layout;
}
});
}
public static IObservable<(IEnumerable<DIYGrid.ColumnGeometry> columns, int row_start, int row_count)?> SubscribeCellDragRect(this DIYGrid grid) {
return Observable.Create<(IEnumerable<DIYGrid.ColumnGeometry> columns, int row_start, int row_count)?>(o => {
return grid.ConnectOnLoaded(init);
IDisposable init() {
if (!( grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter) && presenter.try_find_descendent_of_type(out ScrollViewer scroll) && scroll.try_find_descendent_of_type(out ScrollContentPresenter scroll_content)))
return null;
// init state
var value_container = new DistinctSubject<(IEnumerable<DIYGrid.ColumnGeometry> columns, int row_start, int row_count)?>();
double y_start_drift = 0;
var layout = new List<DIYGrid.ColumnGeometry>();
var layout_x_pts = layout.WrapSelector(cg => cg.left, null);
object _drag_start_item = null;
int _drag_start_index = -1;
Point? drag_start = null, drag_curr = null;
//
scroll.ScrollChanged += on_scroll;
grid.ColumnLayoutChanged += on_layout;
var drag_hook = DragDropHelper.subscribe_drag_coordinates(presenter, grid, use_preview_events: false, mouse_button: MouseButton.Right).Subscribe(pt => {
if (drag_start.HasValue) {
if (!pt.HasValue) {
drag_start = null;
drag_curr = null;
} else {
drag_curr = pt;
}
} else {
if (pt.HasValue) {
var item_container = presenter.ChildAtPoint<ListBoxItem>(pt.Value);
if (item_container == null) {
pt = null;
} else {
_drag_start_item = grid.RowContainerGenerator.ItemFromContainer(item_container);
_drag_start_index = grid.RowContainerGenerator.IndexFromContainer(item_container);
y_start_drift = scroll.VerticalOffset;
}
}
drag_start = pt;
drag_curr = pt;
}
refresh();
});
grid.PreviewMouseUp += (s, e) => e.Handled = e.ChangedButton == MouseButton.Right;
var sub = value_container.Subscribe(o);
return Disposable.Create(dispose);
void on_layout(List<DIYGrid.ColumnGeometry> new_layout) {
layout = new_layout;
layout_x_pts = layout.WrapSelector(cg => cg.left, null);
}
void on_scroll(object sender, ScrollChangedEventArgs e) {
if (drag_start.HasValue)
refresh();
}
void refresh() {
if (!drag_start.HasValue) {
value_container.OnNext(null);
return;
}
var (xstart, ystart) = drag_start.Value.deconstruct();
var (xstop, ystop) = drag_curr.Value.deconstruct();
xstart = MathUtils.within_range(0, presenter.ActualWidth, xstart);
var row_height = grid.RowHeight;
xstop = MathUtils.within_range(0, scroll_content.RenderSize.Width - 1, xstop);
ystop = MathUtils.within_range(0, scroll_content.RenderSize.Height - 1, ystop);
double vertical_diff = ystop + (row_height * (scroll.VerticalOffset - y_start_drift)) - ystart;
int start_index, n_items;
if (vertical_diff > 0) {
double first_item_partial_height = row_height - (ystart % row_height);
n_items = (int)Math.Ceiling((vertical_diff - first_item_partial_height) / row_height) + 1;
start_index = _drag_start_index;
int overflow = start_index + n_items - presenter.Items.Count;
if (overflow > 0)
n_items -= overflow;
} else {
double first_item_partial_height = ystart % row_height;
n_items = -(int)Math.Floor((vertical_diff + first_item_partial_height) / row_height) + 1;
start_index = _drag_start_index - n_items + 1;
if (start_index < 0) {
n_items += start_index;
start_index = 0;
}
}
if (n_items > 0) {
var colstart = find_col(xstart);
var colstop = find_col(xstop);
(colstart, colstop) = MathUtils.get_outliers(colstart, colstop);
(ystart, ystop) = MathUtils.get_outliers(ystart, ystop);
var cols = layout.WrapSubRange(colstart, colstop - colstart + 1);
value_container.OnNext((cols, start_index, n_items));
}
}
int find_col(double x) {
if (layout.Count == 0)
return -1;
int i = CollectionHelper.binary_search(layout, x, l => l.left);
if (i < 0) {
i = ~i - 1;
}
return MathUtils.within_range(0, layout.Count - 1, i);
}
void dispose() {
Disposable.dispose(ref sub);
grid.ColumnLayoutChanged -= on_layout;
}
}
});
}
}
}
@@ -1,126 +0,0 @@
using bts.utils;
using mbts.ui.controls;
using mbts.ui.dpi;
using mbts.ui.extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.spark {
class DIYGridPopupManager {
readonly DIYGrid _grid;
readonly FrameworkElement _owner;
readonly ScrollContentPresenter _scroll_content;
readonly ICollection<Visual> _children;
Popup _active_popup;
bool _is_activating_popup;
public static DIYGridPopupManager create(FrameworkElement owner) =>
create(owner, new VisualCollectionWrapper(new VisualCollection(owner)));
public static DIYGridPopupManager create(FrameworkElement owner, ICollection<Visual> visual_children) =>
owner.try_find_descendent_of_type(out DIYGrid grid) &&
grid.try_find_descendent_of_type(out DIYGridRowsPresenter rows_presenter) &&
rows_presenter.try_find_descendent_of_type(out ScrollContentPresenter scroll_content) ?
new DIYGridPopupManager(owner, grid, scroll_content, visual_children) :
null;
DIYGridPopupManager(FrameworkElement owner, DIYGrid grid, ScrollContentPresenter scroll_content, ICollection<Visual> children) {
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
_grid = grid ?? throw new ArgumentNullException(nameof(grid));
_scroll_content = scroll_content ?? throw new ArgumentNullException(nameof(scroll_content));
_children = children;
}
public Popup active_popup {
get => _active_popup;
set {
if (_active_popup != value) {
_active_popup = value;
if (_active_popup != null) {
_is_activating_popup = true;
_active_popup.IsOpen = false;
_active_popup.IsOpen = true;
_is_activating_popup = false;
}
}
}
}
public async void show(FrameworkElement view, int delay_measure_ms = 100) {
var layout = _grid.GetColumnGeometry().ToList();
if (layout.Count == 0)
return;
var x_origin = layout[0].unclipped_left;
var mouse_pos = Mouse.GetPosition(_scroll_content);
var col_index = CollectionHelper.binary_search(layout, mouse_pos.X + x_origin, cg => cg.left);
if (col_index < 0)
col_index = ~col_index - 1;
col_index = MathUtils.within_range(0, layout.Count - 1, col_index);
var col = layout[col_index];
// determine top left
var top_left = _scroll_content.PointToScreenPixels(
col.unclipped_left - x_origin,
Math.Ceiling(mouse_pos.Y / _grid.RowHeight) * _grid.RowHeight);
if (delay_measure_ms > 0)
await Task.Delay(delay_measure_ms);
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
// popup
var popup = new ZPopup {
PlacementTarget = _grid,
Placement = PlacementMode.Absolute,
Child = view,
IsOpen = true,
HorizontalOffset = top_left.X,
VerticalOffset = top_left.Y,
StaysOpen = false
};
popup.SetBinding(WindowStyle.UnsafeWindowProperty, new Binding {
Source = _owner,
Path = new PropertyPath(WindowStyle.UnsafeWindowProperty)
});
view.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
view.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => popup.IsOpen = false));
view.PreviewMouseDown += on_view_preview_mouse_down;
view.ConnectDataContext<IRequestViewClose>(rvc => {
rvc.RequestClose += on_request_close;
return Disposable.Create(() => rvc.RequestClose -= on_request_close);
void on_request_close(object sender, EventArgs e) {
popup.IsOpen = false;
}
});
var drag_hooks = PopupViewController.enable_drag_move(_owner, popup);
_active_popup = popup;
_children.Add(popup);
_owner.ReleaseMouseCapture();
popup.Closed += on_closed;
FocusManager.SetIsFocusScope(view, true);
Keyboard.Focus(view);
view.Focus();
//
void on_closed(object sender, EventArgs e) {
if (_is_activating_popup && popup == _active_popup) {
return;
}
view.PreviewMouseDown -= on_view_preview_mouse_down;
Disposable.dispose(ref drag_hooks);
popup.ReleaseMouseCapture();
if (Mouse.Captured == _owner)
_owner.ReleaseMouseCapture();
popup.Closed -= on_closed;
_children.Remove(popup);
}
void on_view_preview_mouse_down(object sender, MouseButtonEventArgs e) =>
active_popup = popup;
}
}
}
@@ -55,7 +55,6 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="SelectionMode" Value="Extended"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="GridViewColumnHeader">
@@ -26,12 +26,12 @@ namespace mbts.ui.controls {
}
}
public class HighPerformanceRow<TRowVM> : HighPerformanceRowBase where TRowVM : class, IRowVM {
public class HighPerformanceRow<TRowVM, TColKey> : HighPerformanceRowBase where TRowVM : class, IRowVM {
double _row_height;
ObservableCollection<ColumnBase> _columns;
List<ColumnGeometry> _column_layout = new List<ColumnGeometry>();
Dictionary<string, ColumnGeometry> _column_layout_map = new Dictionary<string, ColumnGeometry>();
VisualDictionary<string, FrameworkElement> _element_cells;
Dictionary<TColKey, ColumnGeometry> _column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
VisualDictionary<TColKey, FrameworkElement> _element_cells;
RecycleBin<DIYGrid> _grid_backing;
RecycleBin<ListBoxItem> _container_backing;
TRowVM _row;
@@ -39,7 +39,7 @@ namespace mbts.ui.controls {
public HighPerformanceRow() : this(DefaultRowHeight) { }
public HighPerformanceRow(double row_height) {
_row_height = row_height;
_element_cells = new VisualDictionary<string, FrameworkElement>(this);
_element_cells = new VisualDictionary<TColKey, FrameworkElement>(this);
_grid_backing = new RecycleBin<DIYGrid>(
g => { g.ColumnLayoutChanged += _on_column_layout_changed; g.RowHeightChanged += _on_row_height_changed; },
g => { g.ColumnLayoutChanged -= _on_column_layout_changed; g.RowHeightChanged -= _on_row_height_changed; });
@@ -123,7 +123,7 @@ namespace mbts.ui.controls {
protected override Size ArrangeOverride(Size finalSize) {
foreach (var kv in _column_layout_map) {
if (_element_cells.TryGetValue(kv.Key, out FrameworkElement ec))
ec.Arrange(kv.Value.to_rect(_row_height));
ec.Arrange(new Rect(new Point(kv.Value.left, 0), new Size(kv.Value.width, _row_height)));
}
return finalSize;
}
@@ -148,18 +148,16 @@ namespace mbts.ui.controls {
}
void _add_columns(IList items) {
foreach (ColumnBase col in items) {
if (col is IFrameworkElementColumn el_col) {
var cell_view = el_col.view_factory();
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.id));
_element_cells.Add(col.id, cell_view);
}
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>()) {
var cell_view = col.view_factory();
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.key));
_element_cells.Add(col.key, cell_view);
}
}
void _remove_columns(IList items) {
foreach (ColumnBase col in items)
_element_cells.Remove(col.id);
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>())
_element_cells.Remove(col.key);
}
void _on_column_layout_changed(List<ColumnGeometry> column_layout) {
@@ -173,10 +171,10 @@ namespace mbts.ui.controls {
}
void _set_column_layout_map() {
_column_layout_map = new Dictionary<string, ColumnGeometry>();
_column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
foreach (var cg in _column_layout) {
if (cg.column is IFrameworkElementColumn)
_column_layout_map.Add(cg.column.id, cg);
if (cg.column is FrameworkElementColumnBase<TColKey> elc)
_column_layout_map.Add(elc.key, cg);
}
}
#endregion
@@ -53,7 +53,11 @@ namespace mbts.ui.controls {
if (col.is_partially_obscured) {
var obscured_width = col.left - col.unclipped_left;
grid.scroll_horizontal(-obscured_width);
return new ColumnGeometry(col.column, col.super_column, col.left, col.left, col.right);
return new ColumnGeometry() {
column = col.column,
left = col.left,
unclipped_left = col.left
};
}
return col;
}
@@ -1,5 +1,6 @@
using bts.proto;
using bts.utils;
using mbts.ui.controls;
using mbts.ui.converters;
using ProtoBuf;
using System;
@@ -23,7 +24,7 @@ namespace mbts.ui.settings {
public static GridSettings Default =>
new GridSettings(
selection_highlight: FallbackValues.selection_highlight.rgb_color(),
selection_highlight: new ColorOpacityProto(FallbackValues.selection_highlight.Color, FallbackValues.selection_highlight.Opacity),
background: _default_background_color, //new ColorOpacityProto(FallbackValues.background_color.Color, FallbackValues.background_color.Opacity));
enable_background_color: _default_enable_background_color);
@@ -44,10 +45,11 @@ namespace mbts.ui.settings {
public static double font_size => _default_font_size;
public static Color font_color => _default_font_color.rgb_color();
public static FontWeight font_weight => FontWeight.FromOpenTypeWeight(_default_font_weight);
public static Color selection_highlight => Colors.MediumSlateBlue.with(0x63);
public static SolidColorBrush selection_highlight => DIYGrid.DefaultSelectionHighlight;
public static uint background_color => _default_background;
}
public static IValueConverter selection_highlight_converter => ValueConverter.create<GridSettings, SolidColorBrush>(gs => gs.selection_highlight.to_brush().frozen());
public static IValueConverter font_family_converter => ValueConverter.create<GridSettings, FontFamily>(gs => new FontFamily(gs.font_family));
public static IValueConverter font_weight_converter => ValueConverter.create<GridSettings, FontWeight>(gs => gs.font_weight);
public static IValueConverter font_size_converter => ValueConverter.create<GridSettings, double>(gs => gs.font_size);
@@ -69,29 +71,8 @@ namespace mbts.ui.settings {
[DefaultValue(_default_font_color)]
readonly uint _font_color;
// selection highlight
ColorOpacityProto _obsolete_selection_highlight_backing;
uint _selection_highlight_backing;
[ProtoMember(5)]
ColorOpacityProto _obsolete_selection_highlight {
get => _obsolete_selection_highlight_backing;
set {
_obsolete_selection_highlight_backing = value;
// only overwrite non-obsolete selection highlight if it is 0 (assume that means 'unassigned').
if (_selection_highlight_backing == 0) {
var color = value.argb_color;
// ensure reasonable opacity
color.A = MathUtils.within_range(color.A,
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.min_alpha,
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.max_alpha);
_selection_highlight_backing = color.rgb_color();
}
}
}
[ProtoMember(10)]
uint _selection_highlight {
get => _selection_highlight_backing;
set => _selection_highlight_backing = value;
}
readonly ColorOpacityProto _selection_highlight;
// columns
[ProtoMember(6)]
List<GridColumnSettings> _columns;
@@ -120,7 +101,7 @@ namespace mbts.ui.settings {
double font_size = _default_font_size,
FontWeight font_weight = default,
uint font_color = _default_font_color,
uint selection_highlight = 0,
ColorOpacityProto? selection_highlight = null,
IEnumerable<GridColumnSettings> columns = null,
IEnumerable<SortingCriteria> sorting_criterion = null,
uint background = _default_background,
@@ -130,7 +111,7 @@ namespace mbts.ui.settings {
_font_size = font_size;
_font_color = font_color;
_font_weight = (ushort)font_weight.ToOpenTypeWeight();
_selection_highlight = selection_highlight;
_selection_highlight = selection_highlight ?? new ColorOpacityProto(0, 1);
_columns = new List<GridColumnSettings>(columns.empty_if_null());
_sorting_criterion = new List<SortingCriteria>(sorting_criterion.empty_if_null());
_background = background;
@@ -142,27 +123,29 @@ namespace mbts.ui.settings {
public uint font_color => _font_color;
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
public Color background => _background.argb_color();
public Color selection_highlight => _selection_highlight.argb_color();
public ColorOpacityProto selection_highlight => _selection_highlight;
public uint background_color => _background;
public bool enable_background_color => _enable_background_color;
public IReadOnlyList<GridColumnSettings> columns => _columns ?? (_columns = new List<GridColumnSettings>(0));
[Obsolete] // TODO: remove sorting_criterion from GridSettings entirely. This should be part of IShellWindow's StartupData, since it is an instance-specific setting, and prob not part of the shared defaults
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = new List<SortingCriteria>(0));
public static GridSettings get_default() =>
new GridSettings(_default_font_family, _default_font_size, FontWeights.Normal, _default_font_color, new ColorOpacityProto(Colors.MediumSlateBlue, 0.4));
public GridSettings with(
IEnumerable<GridColumnSettings> columns = null,
IEnumerable<SortingCriteria> sorting_criterion = null,
string font_family = null,
double font_size = double.NaN,
uint? background = null,
bool? enable_background_color = null,
uint? selection_highlight = null) =>
bool? enable_background_color = null) =>
new GridSettings(
font_family ?? _font_family,
DoubleHelper.IsNaNOrInfinityOr(font_size, 0) ? _font_size : font_size,
font_weight,
_font_color,
selection_highlight ?? _selection_highlight,
_selection_highlight,
columns ?? _columns,
sorting_criterion ?? _sorting_criterion,
background ?? _background,
@@ -1,6 +1,4 @@
using bts.collections;
using bts.settings;
using bts.utils;
using mbts.ui.controls;
using System;
using System.Collections.Generic;
@@ -47,19 +45,6 @@ namespace mbts.ui.settings {
return false;
}
public static GridSettings hide_cols<T>(GridSettings grid, IEnumerable<T> columns) where T : struct =>
grid.with(hide_cols(grid.columns, columns.Select(c => c.ToString())));
public static GridSettings hide_cols(GridSettings grid, IEnumerable<string> columns) =>
grid.with(hide_cols(grid.columns, columns));
public static IEnumerable<GridColumnSettings> hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<string> columns) {
// hide columns who's data is implied by the grouping
var col_hash = new HashSet<string>(columns);
foreach (var c in cols)
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
}
public static TColKey? parse_col_id<TColKey>(string id) where TColKey : struct =>
Enum.TryParse(id, true, out TColKey key) ? key : (TColKey?)null;
@@ -131,26 +116,4 @@ namespace mbts.ui.settings {
return false;
}
}
static class GridSettingsMigrationRules {
public class SelectionHighlightIsTranslucent : ISettingsMigrationRule<GridSettings> {
public const byte min_alpha = 30, max_alpha = 200;
public static bool apply(GridSettings settings, out GridSettings new_settings) {
if (settings != null) {
var sl = settings.selection_highlight;
var a = MathUtils.within_range(sl.A, (byte)30, (byte)200);
if (sl.A != a) {
sl.A = a;
new_settings = settings.with(selection_highlight: sl.rgb_color());
return true;
}
}
new_settings = null;
return false;
}
bool ISettingsMigrationRule<GridSettings>.apply(GridSettings settings, out GridSettings new_settings) =>
apply(settings, out new_settings);
}
}
}
@@ -30,8 +30,7 @@ namespace mbts.ui {
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _background_color;
readonly DelegateProperty<Color> _selection_highlight;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
readonly Func<TPredicateData> _predicate_data_factory;
readonly Func<TPredicateData, IPredicate<TRowData>> _predicate_factory;
@@ -77,7 +76,7 @@ namespace mbts.ui {
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight, nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
}
@@ -89,7 +88,7 @@ namespace mbts.ui {
public FontWeight font_weight => _font_weight.Value;
public double font_size => _font_size.Value;
public SolidColorBrush font_color => _font_color.Value;
public Color selection_highlight => _selection_highlight.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
@@ -134,7 +134,7 @@ namespace mbts.spark {
foreach (IGrouping<TPredicateData, CellAnimation> g in animations)
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
}
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.color.rgb_color(), col_settings,
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings,
background: background_color.color.with((byte)(MathUtils.within_range(0, 1, background_color.opacity) * 255.0)).rgb_color()),
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateData>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
//
@@ -146,7 +146,7 @@ namespace mbts.spark {
_font_family.set(new FontFamily(sd.general.font_family));
_font_weight.set(sd.general.font_weight);
font_color.load_from(sd.general.font_color.rgb_solid_brush());
selection_highlight.color = sd.general.selection_highlight;
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
_show_background_color.set(sd.general.enable_background_color);
if (sd.general.enable_background_color) {
// note that when the OB is moved to allow background colors
@@ -20,8 +20,7 @@ namespace mbts.ui {
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _background_color;
readonly DelegateProperty<Color> _selection_highlight;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
public SimpleGridSettingsManager(ObservableCollection<ColumnBase> columns)
@@ -35,7 +34,7 @@ namespace mbts.ui {
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight, nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
_row_vms = row_vms;
}
@@ -45,8 +44,8 @@ namespace mbts.ui {
public FontWeight font_weight => _font_weight.Value;
public double font_size => _font_size.Value;
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
public Color selection_highlight => _selection_highlight.Value;
public SimpleGridSettingsVM create_vm(GridSettings settings) {
var vm = new SimpleGridSettingsVM(_column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
@@ -120,7 +120,7 @@ namespace mbts.spark {
font_size,
font_weight,
font_color.color.rgb_color(),
selection_highlight.color.rgb_color(),
selection_highlight.to_proto(),
columns.Select(c => c.create()));
if (show_background_color)
rv = rv.with(background: background_color.color.rgb_color());
@@ -134,7 +134,7 @@ namespace mbts.spark {
_font_family.set(new FontFamily(value.font_family));
_font_weight.set(value.font_weight);
font_color.load_from(value.font_color.rgb_solid_brush());
selection_highlight.color = value.selection_highlight;
selection_highlight.color = value.selection_highlight.argb_color;
_show_background_color.set(value.enable_background_color);
if (value.enable_background_color) {
background_color.color = value.background;
@@ -1,144 +0,0 @@
<ResourceDictionary
x:Class="mbts.ui.spark.OrderTicketResources"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Buttons.xaml"/>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Sell</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{StaticResource SellButtonTemplate}"/>
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Buy</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{StaticResource BuyButtonTemplate}"/>
</Style>
<DataTemplate DataType="{x:Type sp:BBOVM}">
<TextBlock Margin="2">
<TextBlock.Inlines>
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10" Text="{Binding bid_size, Mode=OneWay, StringFormat='{}({0})'}"/>
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10" Text="{Binding ask_size, Mode=OneWay, StringFormat='{}({0})'}"/>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type sp:NewOrderTicketVM}">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="120"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}" MaxWidth="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition MinWidth="120"/>
</Grid.ColumnDefinitions>
<usp:OrderModificationControl Grid.ColumnSpan="3"/>
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}"/>
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type sp:ExistingOrderTicketVM}">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="120"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition MinWidth="120"/>
</Grid.ColumnDefinitions>
<usp:OrderModificationControl Grid.ColumnSpan="3"/>
<Button
Grid.Row="2"
Command="{Binding modify_order}"
IsDefault="True"
MinHeight="40"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" FontWeight="Bold">OK</TextBlock>
<TextBlock
Grid.Row="1" HorizontalAlignment="Center" Margin="0,2,0,0" FontSize="9" Text="{Binding dirty_items_text}"
Visibility="{Binding dirty_items_text, Converter={StaticResource DirtyItemsVisibilityConverter}}"
/>
</Grid>
</Button>
<Button
Grid.Row="2"
Grid.Column="2"
Command="ApplicationCommands.Close"
FontWeight="Bold"
IsCancel="True"
MinHeight="40"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
>
Cancel
</Button>
</Grid>
</DataTemplate>
</ResourceDictionary>
@@ -1,10 +0,0 @@
using mbts.ui.utility;
using System.Windows;
namespace mbts.ui.spark {
partial class OrderTicketResources : ResourceDictionary {
public OrderTicketResources() {
InitializeComponent();
}
}
}
@@ -1,10 +1,8 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters">
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter"/>
<uspcv:OrderTypeDisplayTextConverter x:Key="OrderTypeDisplayTextConverter"/>
<Style TargetType="usp:OrderTypeItem">
@@ -28,17 +26,15 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle
<c:Badge
HorizontalAlignment="Left"
VerticalAlignment="Center"
Fill="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="Black"
StrokeThickness="1"
Background="{Binding BadgeFill, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1"
Width="15"
Height="10"
Focusable="False"
RadiusX="{x:Static c:Badge.DefaultCornerRadius}"
RadiusY="{x:Static c:Badge.DefaultCornerRadius}"
IsTabStop="False"
/>
<TextBlock
x:Name="text"
@@ -1,133 +0,0 @@
<ResourceDictionary
x:Class="mbts.ui.spark.AuxiliaryDataResources"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sp="clr-namespace:mbts.spark"
>
<!--Fills-->
<DataTemplate DataType="{x:Type sp:FillsVM}">
<usp:FillsView/>
</DataTemplate>
<!--OrderBook-->
<DataTemplate DataType="{x:Type sp:OrderBookVM}">
<sp:MiniOrderBookView/>
</DataTemplate>
<!--Header (Title Bar)-->
<DataTemplate DataType="{x:Type usp:AuxiliaryDataHeader}">
<Grid MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ContentPresenter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--
Please listen to my angry rant...this is seriously what it takes to auto fit text into a dynamically sized parent container:
1) A hidden textblock, which will be allowed to grow freely, so it's ActualWidth represents the 'desired width.'
- don't forget to add a pixel of padding, because setting the MaxWidth to the ActualWidth of a perfectly
measured TextBlock with the same Text/Typeface may still trigger the TextTrimming ellipsis.
2) A canvas (with 0 width) to wrap the hidden textblock, so that its width does not affect that of the parent.
3) A ridiculously long MultiBinding, that has child bindings to the ActualWidth of each sibling visual element,
which are summed by the Converter to yield the total occupied width.
4) Finally: a visible textblock, with a MaxWidth bound to the minimum of the 'desired width' and the 'available width - occupied width.'
5) A new computer, to replace the one that was defenestrated out of frustration on the long journey to this solution.
-->
<Canvas>
<TextBlock x:Name="PART_ContextHidden" FontWeight="Bold" Padding="1,0,0,0" Text="{Binding context}" Visibility="Hidden"/>
</Canvas>
<TextBlock
x:Name="PART_Context"
FontWeight="Bold"
HorizontalAlignment="Left"
Margin="4,0,0,0"
Text="{Binding context}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"
>
<TextBlock.Tag>
<MultiBinding>
<MultiBinding.Converter>
<cv:DoubleDynamicSubtractionConverter Minimum="0"/>
</MultiBinding.Converter>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=ContentPresenter}"/>
<Binding Path="ActualWidth" ElementName="PART_KeepOpen"/>
<Binding Path="ActualWidth" ElementName="PART_Close"/>
<Binding Path="ActualWidth" ElementName="PART_Title"/>
<Binding Source="{u:Double 20}"/>
</MultiBinding>
</TextBlock.Tag>
<TextBlock.MaxWidth>
<MultiBinding Converter="{x:Static cv:MinimumConverter.Instance}">
<Binding Path="ActualWidth" ElementName="PART_ContextHidden"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</TextBlock.MaxWidth>
</TextBlock>
<TextBlock
x:Name="PART_Title"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="4,0,0,0"
FontWeight="Bold"
Text="{Binding title}"
ToolTip="{Binding title}"
>
<TextBlock.Foreground>
<SolidColorBrush Color="{Binding title_foreground}"/>
</TextBlock.Foreground>
</TextBlock>
<CheckBox x:Name="PART_KeepOpen" Grid.Column="2" VerticalAlignment="Center" Margin="4,0,0,0" Focusable="False" IsChecked="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType=Popup}}">Keep Open</CheckBox>
<Button x:Name="PART_Close" Grid.Column="3" HorizontalAlignment="Right" Margin="4" Command="{x:Static ApplicationCommands.Close}" CommandTarget="{Binding RelativeSource={RelativeSource AncestorType=HeaderedContentControl}}" Style="{StaticResource RoundCloseButton}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type usp:OrderTicketHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding bbo}"/>
<CheckBox Grid.Column="2" VerticalAlignment="Center" Margin="4,0,0,0" Focusable="False" IsChecked="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType=Popup}}">Keep Open</CheckBox>
<Button Grid.Column="3" HorizontalAlignment="Right" Margin="4" Command="{x:Static ApplicationCommands.Close}" CommandTarget="{Binding RelativeSource={RelativeSource AncestorType=HeaderedContentControl}}" Style="{StaticResource RoundCloseButton}"/>
</Grid>
</DataTemplate>
<ControlTemplate x:Key="PopupChildTemplate" TargetType="HeaderedContentControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter ContentSource="Header" Width="{Binding ActualWidth, ElementName=Content}"/>
<ContentPresenter
x:Name="Content"
Grid.Row="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Grid>
</Border>
</ControlTemplate>
<Style x:Key="PopupChildStyleKey" TargetType="HeaderedContentControl">
<Setter Property="Background" Value="{DynamicResource CommonBackground}"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="MaxHeight" Value="300"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Template" Value="{StaticResource PopupChildTemplate}"/>
</Style>
</ResourceDictionary>
@@ -1,36 +0,0 @@
using mbts.spark;
using mbts.ui.shell;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.spark {
partial class AuxiliaryDataResources : ResourceDictionary {
public AuxiliaryDataResources() =>
InitializeComponent();
public static string PopupChildStyleKey => nameof(PopupChildStyleKey);
}
class AuxiliaryDataHeader : ViewModelBase {
string
_title,
_context;
Color _title_foreground;
bool _keep_open;
public Color title_foreground { get => _title_foreground; set => Set(ref _title_foreground, value); }
public string title { get => _title; set => Set(ref _title, value); }
public string context { get => _context; set => Set(ref _context, value); }
public bool keep_open { get => _keep_open; set => Set(ref _keep_open, value); }
}
class OrderTicketHeader : ViewModelBase {
bool _keep_open;
public BBOVM bbo { get; set; }
public bool keep_open { get => _keep_open; set => Set(ref _keep_open, value); }
}
class ConstrainedTextBlock : TextBlock {
}
}
@@ -1,29 +1,9 @@
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.controls {
static class PopupViewController {
#region ButtonSource
public static readonly DependencyProperty ButtonSourceProperty =
DependencyProperty.RegisterAttached("ButtonSource", typeof(ButtonBase), typeof(PopupViewController),
new PropertyMetadata(OnButtonSourceChanged));
static void OnButtonSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is Popup popup)
popup.SetValue(ButtonSourceDependencyPropertyKey,
e.NewValue is ButtonBase button ? attach(popup, button) : null);
}
public static bool GetButtonSource(DependencyObject d) => (bool)d.GetValue(ButtonSourceProperty);
public static void SetButtonSource(DependencyObject d, bool value) => d.SetValue(ButtonSourceProperty, value);
static readonly DependencyPropertyKey ButtonSourceDependencyPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("ButtonSourceDependency", typeof(IDisposable), typeof(PopupViewController),
new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOld));
#endregion
public static IDisposable attach(Popup popup, ButtonBase open_cmd_source, bool auto_focus_popup_root_on_open = false) {
/// There is a paradoxical corner case handled here that occurs when the user clicks the button to 'close' the popup.
/// Conventional logic might dictate that a <see cref="ToggleButton "/> be used to bind <see cref="ToggleButton.IsChecked"/> to <see cref="Popup.IsOpen"/>,
@@ -54,38 +34,5 @@ namespace mbts.ui.controls {
ignore_open_cmd = true;
}
}
public static IDisposable enable_drag_move(FrameworkElement surface, Popup popup) {
Point? last_drag_pt = null;
return DragDropHelper.subscribe_drag_coordinates(surface, popup.Child as FrameworkElement).Subscribe(drag_pt => {
if (drag_pt.HasValue) {
var d = drag_pt.Value;
var rs = surface.RenderSize;
if (last_drag_pt.HasValue) {
popup.HorizontalOffset += d.X - last_drag_pt.Value.X;
popup.VerticalOffset += d.Y - last_drag_pt.Value.Y;
last_drag_pt = d;
} else {
popup.SetCurrentValue(Popup.StaysOpenProperty, true);
last_drag_pt = d;
Mouse.Capture(surface);
}
} else {
last_drag_pt = null;
if (popup.StaysOpen) {
// popup.Child must be focused BEFORE the StaysOpen binding target (i.e., the popup itself) is updated;
// Otherwise, if StaysOpen becomes false as a result of the update,
// the popup would close in response to the 'LostFocus' event generated by the late Child.Focus call.
// A reasonable person may ask, "Well why must the Popup.Child be focused at all?"
// The answer is that the popup must be focused, to automatically close when it loses focus.
popup.Child.Focus();
surface.ReleaseMouseCapture();
popup.GetBindingExpression(Popup.StaysOpenProperty)?.UpdateTarget();
} else {
surface.ReleaseMouseCapture();
}
}
});
}
}
}
}
@@ -1,107 +0,0 @@
using mbts.ui.native;
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
namespace mbts.ui.controls {
/// <summary>
/// Popup with an adjustable 'Z' order (ordinary Popup control is always top-most).
/// </summary>
class ZPopup : Popup {
IDisposable _window_sub;
Window _window;
bool _is_top_most;
static ZPopup() =>
ChildProperty.OverrideMetadata(typeof(ZPopup), new FrameworkPropertyMetadata(OnChildChanged));
public ZPopup() =>
WindowStyle.AddUnsafeWindowChangedHandler(this, (s, e) => _on_window(e.NewValue));
#region IsTopMost
public static readonly DependencyProperty IsTopMostProperty =
WPFHelper.create_dp<bool, ZPopup>(nameof(IsTopMost), (o, v) => o._set_top_most_state(v));
public bool IsTopMost {
get { return (bool)GetValue(IsTopMostProperty); }
set { SetValue(IsTopMostProperty, value); }
}
#endregion
protected override void OnOpened(EventArgs e) =>
_set_top_most_state(IsTopMost);
static void OnChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ZPopup zp) {
if (e.OldValue is UIElement old_child)
old_child.PreviewMouseLeftButtonDown -= zp.OnChildPreviewMouseLeftButtonDown;
if (e.NewValue is UIElement new_child)
new_child.PreviewMouseLeftButtonDown += zp.OnChildPreviewMouseLeftButtonDown;
}
}
void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) =>
SetCurrentValue(IsTopMostProperty, true);
void _on_window(Window win) {
Disposable.dispose(ref _window_sub);
_window = win;
Thread.MemoryBarrier();
if (win != null) {
var src = Observable.Create<bool>(o => {
o.OnNext(win.IsActive);
win.Activated += on_activated;
win.Deactivated += on_deactivated;
return Disposable.Create(() => {
win.Activated -= on_activated;
win.Deactivated -= on_deactivated;
});
void on_activated(object sender, EventArgs e) => o.OnNext(true);
void on_deactivated(object sender, EventArgs e) => o.OnNext(false);
})
.SubscribeOn(win.Dispatcher)
.OnDispatcher();
_window_sub = src.Subscribe(v => {
if (v == _is_top_most) {
/// <see cref="IsTopMost"/> is already equal to this value;
/// explicitly refresh the child's z index.
_set_top_most_state(v);
} else {
/// <see cref="_set_top_most_state(bool)"/> will be called in the dependency prop changed callback
SetCurrentValue(IsTopMostProperty, v);
}
});
}
}
async void _set_top_most_state(bool value) {
_is_top_most = value;
if (Child != null && (PresentationSource.FromVisual(Child) is HwndSource hwnd_source)) {
var hwnd = hwnd_source.Handle;
if (NativeMethods.GetWindowRect(hwnd, out var rect)) {
if (value) {
var win = _window;
if (win != null) {
// bring popup to the front by activating parent window
await win.Dispatcher.InvokeAsync(() => {
// as this is an async op,
// check that the window hasn't changed, and IsTopMost is still true before activating
if (win == _window && _is_top_most)
win.Activate();
});
} else {
// bring popup [alone]to the front.
NativeMethods.SetWindowPos(hwnd, NativeMethods.HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, SWP.TOPMOST_FLAGS);
}
} else {
NativeMethods.SetWindowPos(hwnd, NativeMethods.HWND_NOTOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, SWP.TOPMOST_FLAGS);
}
}
}
}
}
}
Loaded 100 of 402 files, more files were not shown because too many files have changed in this diff. Show more