Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d942676765 | ||
|
|
0c4cdd0bc9 | ||
|
|
00d8ea9322 | ||
|
|
a586b75226 | ||
|
|
c6039723da | ||
|
|
6f26164072 | ||
|
|
7b01fdcb23 | ||
|
|
d6df085d8e | ||
|
|
c6f2fe9b13 | ||
|
|
c56b41bf47 | ||
|
|
484c7a98a8 | ||
|
|
559b6f8fd1 | ||
|
|
41fa3046f1 | ||
|
|
fdf21c71bb | ||
|
|
f03453f7c5 | ||
|
|
9ebdb107fa | ||
|
|
057d5df6da | ||
|
|
188a226165 | ||
|
|
9aba824be0 | ||
|
|
a958be45dd | ||
|
|
7fd0375283 | ||
|
|
dc2879e2ff | ||
|
|
7fa75b8090 | ||
|
|
5e58aaf199 | ||
|
|
54231e2d74 | ||
|
|
0042835842 | ||
|
|
a53d48a6e3 | ||
|
|
d3ffc9eed5 | ||
|
|
dcfa809b9e | ||
|
|
fa31c2ef11 | ||
|
|
7c28938ba6 | ||
|
|
0a426b84e3 | ||
|
|
45f5e485d3 | ||
|
|
853f8d2e84 | ||
|
|
d1784355d0 | ||
|
|
f045d272c1 | ||
|
|
faa6528400 | ||
|
|
be33ecc798 | ||
|
|
37f3550831 | ||
|
|
e2d454da1e | ||
|
|
7af2467713 | ||
|
|
d8e203264d | ||
|
|
4a0cefd240 | ||
|
|
5330000e12 | ||
|
|
e5aef7ee5b | ||
|
|
1d51a063ea | ||
|
|
da3dc9f477 | ||
|
|
f437d50041 | ||
|
|
c7bbcc1738 | ||
|
|
3bdde95b42 | ||
|
|
52e6be5d60 | ||
|
|
bda76d0234 | ||
|
|
7cb08ba90a | ||
|
|
ee6d05c737 | ||
|
|
62a5ee94e8 | ||
|
|
f6e12d1664 | ||
|
|
d022080b80 | ||
|
|
44acb1caac | ||
|
|
28347f1074 | ||
|
|
7826a1d0b4 | ||
|
|
975edb48df | ||
|
|
c2a5c278e9 | ||
|
|
f895e69aa9 | ||
|
|
7c050a9e10 | ||
|
|
38087d21d8 | ||
|
|
4b2e49c4aa | ||
|
|
e31c0a8112 | ||
|
|
876d6e0981 | ||
|
|
cb2dac6126 | ||
|
|
9f2ac024cb | ||
|
|
a3cc1c652f | ||
|
|
7ab6fd273e | ||
|
|
92a8cb507a | ||
|
|
ea87dcb52a | ||
|
|
467a66565d | ||
|
|
7d47361446 | ||
|
|
9a95b5ed34 | ||
|
|
0070c1f1cc | ||
|
|
cd4664a012 | ||
|
|
b1b8d79706 | ||
|
|
002832a87d | ||
|
|
958212ea0c | ||
|
|
0013d529da | ||
|
|
23f8d0bbbb | ||
|
|
3c72760a3f | ||
|
|
8844f60cd0 | ||
|
|
a52b477b0c | ||
|
|
dbd0bfed10 | ||
|
|
ee25d3ed9a | ||
|
|
d662a60f6a | ||
|
|
c0a80182e6 | ||
|
|
62e01e43e2 | ||
|
|
7953cc1209 | ||
|
|
4360a1154c | ||
|
|
1cb6066aea | ||
|
|
67227320c9 | ||
|
|
e85d7b5d0c | ||
|
|
5dd2c14fba | ||
|
|
143f660f37 | ||
|
|
4107ac093b | ||
|
|
dcf116fe82 | ||
|
|
9c571cc16d | ||
|
|
aa8e32ee43 | ||
|
|
c90752b4f2 | ||
|
|
4d0a793ecc | ||
|
|
f97ccf249f | ||
|
|
659bf6b208 | ||
|
|
33312a4bf8 | ||
|
|
929a93b1f7 | ||
|
|
f49e41c5c0 | ||
|
|
c705867218 | ||
|
|
2d3e942f93 | ||
|
|
d965674ff5 | ||
|
|
87f99d42bf | ||
|
|
a174336baf | ||
|
|
746e858f76 | ||
|
|
48702ce266 | ||
|
|
8ba354eda8 | ||
|
|
e4f5531855 | ||
|
|
74b18cf71a | ||
|
|
ae2712c9ac | ||
|
|
d62bde8158 | ||
|
|
53ea5ddf81 | ||
|
|
65d3b858b3 | ||
|
|
1e8fd9589b | ||
|
|
c331c8dde5 | ||
|
|
9e0b95a1ad | ||
|
|
21a47e2d43 | ||
|
|
8cbe6d7199 | ||
|
|
58481c9b17 | ||
|
|
1b5256d699 | ||
|
|
1e64f930d9 | ||
|
|
9630851f1f | ||
|
|
46878f009a | ||
|
|
ef77826042 | ||
|
|
344f82dc8f | ||
|
|
bc362b9778 | ||
|
|
f17b2cb06d | ||
|
|
1d5878e2f5 | ||
|
|
5325c372ba | ||
|
|
42cfba5ccc | ||
|
|
9e178cf4a1 | ||
|
|
74cac5815c | ||
|
|
c3f4183531 | ||
|
|
6de5416394 | ||
|
|
1a947097a5 | ||
|
|
50abbd6aea | ||
|
|
f324504df1 | ||
|
|
931d5dad01 | ||
|
|
ef094b891a | ||
|
|
b47055d814 | ||
|
|
1a760a579a | ||
|
|
f6b5635715 | ||
|
|
1a7d0d75a8 | ||
|
|
e41a9351c2 | ||
|
|
7a1ffa1ea9 | ||
|
|
db34468bc4 | ||
|
|
f5d7ea9a47 | ||
|
|
dc8c0df0a4 | ||
|
|
d25d91038d | ||
|
|
700f1d97fd | ||
|
|
b3d3071316 | ||
|
|
88e352457f | ||
|
|
f1bb6af757 | ||
|
|
5b1cf890e8 | ||
|
|
97a1cf0162 | ||
|
|
c254248886 | ||
|
|
1821ff8cc7 | ||
|
|
dd397987ca | ||
|
|
9b2b320255 | ||
|
|
655a89289a | ||
|
|
911cf8a19c | ||
|
|
7d9aaeac55 | ||
|
|
5aaa5a3360 | ||
|
|
6546ee531f | ||
|
|
9ea0e214b6 | ||
|
|
0ffe8ba972 | ||
|
|
41eb89ed35 | ||
|
|
54d31c345a | ||
|
|
9ae087eca1 | ||
|
|
08d8dcd53f | ||
|
|
1a279298fd | ||
|
|
479eba940e | ||
|
|
ae3e721f62 | ||
|
|
0833dce43d | ||
|
|
4aa64a7b5d | ||
|
|
459d097239 | ||
|
|
188616d1ce | ||
|
|
9ef1fdb795 | ||
|
|
e241d13edc | ||
|
|
df7d791ba1 | ||
|
|
47b2e917c1 | ||
|
|
c2864d1715 | ||
|
|
2769de6f7e | ||
|
|
ef3b39361a | ||
|
|
fa2a2e2107 | ||
|
|
3005dfdc0f | ||
|
|
4fd8d9dc5b | ||
|
|
c948b981cf | ||
|
|
8e51b05941 | ||
|
|
75568e6c75 | ||
|
|
6e3c880ea2 | ||
|
|
ded957a0ef | ||
|
|
b0db7ad4b3 | ||
|
|
a29731cb3c | ||
|
|
9b187797f8 | ||
|
|
56ba30e7f4 | ||
|
|
f776099c98 | ||
|
|
2825ad833f | ||
|
|
795915fddc | ||
|
|
1ff0fe0208 | ||
|
|
6d03493bfb | ||
|
|
080c8cdaa6 | ||
|
|
05741ceab0 | ||
|
|
6db1aeea3f | ||
|
|
a6214a47b5 | ||
|
|
2da1644f8e | ||
|
|
e15764eb2b | ||
|
|
08a6daab00 | ||
|
|
ef03b87357 | ||
|
|
0f339f603b | ||
|
|
49796a5c33 | ||
|
|
51cc64f031 | ||
|
|
42b4345485 | ||
|
|
20d77d1cf3 | ||
|
|
9dd6fc8832 | ||
|
|
3a5f6b331d | ||
|
|
d09dbb9422 | ||
|
|
81afd71d84 | ||
|
|
da21c57cca | ||
|
|
d6fafee64c | ||
|
|
0ed884a5ab | ||
|
|
1822ed6aa2 | ||
|
|
eabfb6a695 | ||
|
|
58d0a3c688 | ||
|
|
8b3d4ba9aa | ||
|
|
8ff94037eb | ||
|
|
065f667a79 | ||
|
|
f61b69f6bd | ||
|
|
9ec85c324c | ||
|
|
5ab506e9cd | ||
|
|
e51d7de309 | ||
|
|
28307b0681 | ||
|
|
5af68ccfd0 | ||
|
|
4002f80ede | ||
|
|
65915a9784 | ||
|
|
7259397e02 | ||
|
|
281b4e10b0 | ||
|
|
b17a0b1eea | ||
|
|
d7e2c87830 | ||
|
|
22b1bfa777 | ||
|
|
4ea9c6c28d | ||
|
|
2cb5cddb91 | ||
|
|
cc0dd649ea | ||
|
|
49e24804f7 | ||
|
|
9e3b3879d0 | ||
|
|
7958ce236f | ||
|
|
f3c55f0981 | ||
|
|
dc9df4af09 | ||
|
|
add6f99189 | ||
|
|
37c6b5d46d | ||
|
|
0ff7adf73b | ||
|
|
3ffc130889 | ||
|
|
69d75292ee | ||
|
|
d6539348ea | ||
|
|
4d00be1ab5 | ||
|
|
505b01cdcb | ||
|
|
e8b210913c | ||
|
|
2e10e997b0 | ||
|
|
a1b189fcb1 | ||
|
|
530975d9de | ||
|
|
7a48e5dd5c | ||
|
|
2842aea933 | ||
|
|
f560a9e603 | ||
|
|
f97d156020 | ||
|
|
d8922be0ad | ||
|
|
5634ada367 | ||
|
|
94d249edbd | ||
|
|
1c29ac0ec5 | ||
|
|
e7e280bad8 | ||
|
|
7ca11c3eee | ||
|
|
caf6ac029c | ||
|
|
4c0fa81963 | ||
|
|
720c1595e0 | ||
|
|
d20e937a79 | ||
|
|
bedd0051a9 | ||
|
|
09bed1a713 | ||
|
|
ebb53c5035 | ||
|
|
243b82a6d7 | ||
|
|
bd4f840d27 | ||
|
|
de893d2175 | ||
|
|
b7a99047ed | ||
|
|
98eb65c99f | ||
|
|
d1f346b28c | ||
|
|
39bf6ebaa5 | ||
|
|
9011df884e | ||
|
|
92490e1a75 | ||
|
|
8ee6d6d860 | ||
|
|
543dfa8509 | ||
|
|
a8bc7a071e | ||
|
|
02071c1289 | ||
|
|
ce80e12451 | ||
|
|
a04df917d4 | ||
|
|
0a7d8f931b | ||
|
|
77105029d3 | ||
|
|
5fe0b0c58b | ||
|
|
33658f13f6 | ||
|
|
f77f7b091d | ||
|
|
4f9111b8a0 | ||
|
|
94cda9a09b | ||
|
|
469ee033b2 | ||
|
|
29afa7f3b1 | ||
|
|
45283593ce | ||
|
|
00d91353a4 | ||
|
|
451fb67195 |
No files matched your search
@@ -24,4 +24,7 @@ project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/logs/
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
.fake
|
||||
*.fsx.lock
|
||||
src/lib/orderman/perf_analysis/logs/latency_data/
|
||||
trace_data/
|
||||
Vendored
+13
-2
@@ -42,7 +42,18 @@
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": ["test","test_filter"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
@@ -187,7 +198,7 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test", "[md]"],
|
||||
"args": ["test","Co2RelayTests"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
|
||||
+1
-3
@@ -40,7 +40,7 @@ else ()
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG -O3")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/web)
|
||||
@@ -54,8 +54,6 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<head>
|
||||
<!-- 2018-10-08 Mon 18:50 -->
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>‎</title>
|
||||
<meta name="generator" content="Org mode" />
|
||||
<meta name="author" content="ppinto" />
|
||||
<style type="text/css">
|
||||
<!--/*--><![CDATA[/*><!--*/
|
||||
.title { text-align: center;
|
||||
margin-bottom: .2em; }
|
||||
.subtitle { text-align: center;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
margin-top:0; }
|
||||
.todo { font-family: monospace; color: red; }
|
||||
.done { font-family: monospace; color: green; }
|
||||
.priority { font-family: monospace; color: orange; }
|
||||
.tag { background-color: #eee; font-family: monospace;
|
||||
padding: 2px; font-size: 80%; font-weight: normal; }
|
||||
.timestamp { color: #bebebe; }
|
||||
.timestamp-kwd { color: #5f9ea0; }
|
||||
.org-right { margin-left: auto; margin-right: 0px; text-align: right; }
|
||||
.org-left { margin-left: 0px; margin-right: auto; text-align: left; }
|
||||
.org-center { margin-left: auto; margin-right: auto; text-align: center; }
|
||||
.underline { text-decoration: underline; }
|
||||
#postamble p, #preamble p { font-size: 90%; margin: .2em; }
|
||||
p.verse { margin-left: 3%; }
|
||||
pre {
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 3px 3px 3px #eee;
|
||||
padding: 8pt;
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
margin: 1.2em;
|
||||
}
|
||||
pre.src {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
padding-top: 1.2em;
|
||||
}
|
||||
pre.src:before {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
top: -10px;
|
||||
right: 10px;
|
||||
padding: 3px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
pre.src:hover:before { display: inline;}
|
||||
/* Languages per Org manual */
|
||||
pre.src-asymptote:before { content: 'Asymptote'; }
|
||||
pre.src-awk:before { content: 'Awk'; }
|
||||
pre.src-C:before { content: 'C'; }
|
||||
/* pre.src-C++ doesn't work in CSS */
|
||||
pre.src-clojure:before { content: 'Clojure'; }
|
||||
pre.src-css:before { content: 'CSS'; }
|
||||
pre.src-D:before { content: 'D'; }
|
||||
pre.src-ditaa:before { content: 'ditaa'; }
|
||||
pre.src-dot:before { content: 'Graphviz'; }
|
||||
pre.src-calc:before { content: 'Emacs Calc'; }
|
||||
pre.src-emacs-lisp:before { content: 'Emacs Lisp'; }
|
||||
pre.src-fortran:before { content: 'Fortran'; }
|
||||
pre.src-gnuplot:before { content: 'gnuplot'; }
|
||||
pre.src-haskell:before { content: 'Haskell'; }
|
||||
pre.src-hledger:before { content: 'hledger'; }
|
||||
pre.src-java:before { content: 'Java'; }
|
||||
pre.src-js:before { content: 'Javascript'; }
|
||||
pre.src-latex:before { content: 'LaTeX'; }
|
||||
pre.src-ledger:before { content: 'Ledger'; }
|
||||
pre.src-lisp:before { content: 'Lisp'; }
|
||||
pre.src-lilypond:before { content: 'Lilypond'; }
|
||||
pre.src-lua:before { content: 'Lua'; }
|
||||
pre.src-matlab:before { content: 'MATLAB'; }
|
||||
pre.src-mscgen:before { content: 'Mscgen'; }
|
||||
pre.src-ocaml:before { content: 'Objective Caml'; }
|
||||
pre.src-octave:before { content: 'Octave'; }
|
||||
pre.src-org:before { content: 'Org mode'; }
|
||||
pre.src-oz:before { content: 'OZ'; }
|
||||
pre.src-plantuml:before { content: 'Plantuml'; }
|
||||
pre.src-processing:before { content: 'Processing.js'; }
|
||||
pre.src-python:before { content: 'Python'; }
|
||||
pre.src-R:before { content: 'R'; }
|
||||
pre.src-ruby:before { content: 'Ruby'; }
|
||||
pre.src-sass:before { content: 'Sass'; }
|
||||
pre.src-scheme:before { content: 'Scheme'; }
|
||||
pre.src-screen:before { content: 'Gnu Screen'; }
|
||||
pre.src-sed:before { content: 'Sed'; }
|
||||
pre.src-sh:before { content: 'shell'; }
|
||||
pre.src-sql:before { content: 'SQL'; }
|
||||
pre.src-sqlite:before { content: 'SQLite'; }
|
||||
/* additional languages in org.el's org-babel-load-languages alist */
|
||||
pre.src-forth:before { content: 'Forth'; }
|
||||
pre.src-io:before { content: 'IO'; }
|
||||
pre.src-J:before { content: 'J'; }
|
||||
pre.src-makefile:before { content: 'Makefile'; }
|
||||
pre.src-maxima:before { content: 'Maxima'; }
|
||||
pre.src-perl:before { content: 'Perl'; }
|
||||
pre.src-picolisp:before { content: 'Pico Lisp'; }
|
||||
pre.src-scala:before { content: 'Scala'; }
|
||||
pre.src-shell:before { content: 'Shell Script'; }
|
||||
pre.src-ebnf2ps:before { content: 'ebfn2ps'; }
|
||||
/* additional language identifiers per "defun org-babel-execute"
|
||||
in ob-*.el */
|
||||
pre.src-cpp:before { content: 'C++'; }
|
||||
pre.src-abc:before { content: 'ABC'; }
|
||||
pre.src-coq:before { content: 'Coq'; }
|
||||
pre.src-groovy:before { content: 'Groovy'; }
|
||||
/* additional language identifiers from org-babel-shell-names in
|
||||
ob-shell.el: ob-shell is the only babel language using a lambda to put
|
||||
the execution function name together. */
|
||||
pre.src-bash:before { content: 'bash'; }
|
||||
pre.src-csh:before { content: 'csh'; }
|
||||
pre.src-ash:before { content: 'ash'; }
|
||||
pre.src-dash:before { content: 'dash'; }
|
||||
pre.src-ksh:before { content: 'ksh'; }
|
||||
pre.src-mksh:before { content: 'mksh'; }
|
||||
pre.src-posh:before { content: 'posh'; }
|
||||
/* Additional Emacs modes also supported by the LaTeX listings package */
|
||||
pre.src-ada:before { content: 'Ada'; }
|
||||
pre.src-asm:before { content: 'Assembler'; }
|
||||
pre.src-caml:before { content: 'Caml'; }
|
||||
pre.src-delphi:before { content: 'Delphi'; }
|
||||
pre.src-html:before { content: 'HTML'; }
|
||||
pre.src-idl:before { content: 'IDL'; }
|
||||
pre.src-mercury:before { content: 'Mercury'; }
|
||||
pre.src-metapost:before { content: 'MetaPost'; }
|
||||
pre.src-modula-2:before { content: 'Modula-2'; }
|
||||
pre.src-pascal:before { content: 'Pascal'; }
|
||||
pre.src-ps:before { content: 'PostScript'; }
|
||||
pre.src-prolog:before { content: 'Prolog'; }
|
||||
pre.src-simula:before { content: 'Simula'; }
|
||||
pre.src-tcl:before { content: 'tcl'; }
|
||||
pre.src-tex:before { content: 'TeX'; }
|
||||
pre.src-plain-tex:before { content: 'Plain TeX'; }
|
||||
pre.src-verilog:before { content: 'Verilog'; }
|
||||
pre.src-vhdl:before { content: 'VHDL'; }
|
||||
pre.src-xml:before { content: 'XML'; }
|
||||
pre.src-nxml:before { content: 'XML'; }
|
||||
/* add a generic configuration mode; LaTeX export needs an additional
|
||||
(add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */
|
||||
pre.src-conf:before { content: 'Configuration File'; }
|
||||
|
||||
table { border-collapse:collapse; }
|
||||
caption.t-above { caption-side: top; }
|
||||
caption.t-bottom { caption-side: bottom; }
|
||||
td, th { vertical-align:top; }
|
||||
th.org-right { text-align: center; }
|
||||
th.org-left { text-align: center; }
|
||||
th.org-center { text-align: center; }
|
||||
td.org-right { text-align: right; }
|
||||
td.org-left { text-align: left; }
|
||||
td.org-center { text-align: center; }
|
||||
dt { font-weight: bold; }
|
||||
.footpara { display: inline; }
|
||||
.footdef { margin-bottom: 1em; }
|
||||
.figure { padding: 1em; }
|
||||
.figure p { text-align: center; }
|
||||
.inlinetask {
|
||||
padding: 10px;
|
||||
border: 2px solid gray;
|
||||
margin: 10px;
|
||||
background: #ffffcc;
|
||||
}
|
||||
#org-div-home-and-up
|
||||
{ text-align: right; font-size: 70%; white-space: nowrap; }
|
||||
textarea { overflow-x: auto; }
|
||||
.linenr { font-size: smaller }
|
||||
.code-highlighted { background-color: #ffff00; }
|
||||
.org-info-js_info-navigation { border-style: none; }
|
||||
#org-info-js_console-label
|
||||
{ font-size: 10px; font-weight: bold; white-space: nowrap; }
|
||||
.org-info-js_search-highlight
|
||||
{ background-color: #ffff00; color: #000000; font-weight: bold; }
|
||||
.org-svg { width: 90%; }
|
||||
/*]]>*/-->
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
/*
|
||||
@licstart The following is the entire license notice for the
|
||||
JavaScript code in this tag.
|
||||
|
||||
Copyright (C) 2012-2018 Free Software Foundation, Inc.
|
||||
|
||||
The JavaScript code in this tag is free software: you can
|
||||
redistribute it and/or modify it under the terms of the GNU
|
||||
General Public License (GNU GPL) as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option)
|
||||
any later version. The code is distributed WITHOUT ANY WARRANTY;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
||||
|
||||
As additional permission under GNU GPL version 3 section 7, you
|
||||
may distribute non-source (e.g., minimized or compacted) forms of
|
||||
that code without the copy of the GNU GPL normally required by
|
||||
section 4, provided you include this license notice and a URL
|
||||
through which recipients can access the Corresponding Source.
|
||||
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this tag.
|
||||
*/
|
||||
<!--/*--><![CDATA[/*><!--*/
|
||||
function CodeHighlightOn(elem, id)
|
||||
{
|
||||
var target = document.getElementById(id);
|
||||
if(null != target) {
|
||||
elem.cacheClassElem = elem.className;
|
||||
elem.cacheClassTarget = target.className;
|
||||
target.className = "code-highlighted";
|
||||
elem.className = "code-highlighted";
|
||||
}
|
||||
}
|
||||
function CodeHighlightOff(elem, id)
|
||||
{
|
||||
var target = document.getElementById(id);
|
||||
if(elem.cacheClassElem)
|
||||
elem.className = elem.cacheClassElem;
|
||||
if(elem.cacheClassTarget)
|
||||
target.className = elem.cacheClassTarget;
|
||||
}
|
||||
/*]]>*///-->
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<p>
|
||||
We are going to experiment with our development process, getting away
|
||||
from cycles and towards more continuous development. The main idea is
|
||||
to get rid of the prod and QA branches and instead deploy directly
|
||||
from master, which we keep deployment ready at all times. Development
|
||||
occurs on private branches, each branch mapped to a separately
|
||||
deploy-able project. Each of projects will be managed more or less
|
||||
independently and will include specification, development and
|
||||
validation stages. At any particular time we might have anywhere from
|
||||
1 to 6 projects in flight each of which continuously pulls from master,
|
||||
but does not push back to master until the code is ready to be
|
||||
deployed to production. The point at which a project is ready to be
|
||||
deployed then takes roughly the place of the QA phase of our old cycle
|
||||
mode. At this time, we will do a PR, and go through a series of
|
||||
validation steps, to make sure the work meets our standards. This
|
||||
validation includes:
|
||||
</p>
|
||||
|
||||
<ul class="org-ul">
|
||||
<li>Code Reviews</li>
|
||||
<li>Acceptance Testing (including UI and business tests)</li>
|
||||
<li>Automated System testing (to be developed)</li>
|
||||
<li>A mini-qa, focused on the feature, by someone not directly involved
|
||||
in its implementation</li>
|
||||
<li>If possible, deployment to a beta customer.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Once these QA gates are passed, we will update the release notes, tag
|
||||
a new version, integrate to master and then ideally immediately deploy
|
||||
to a customer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The main goal with this change is to reduce the latency from feature
|
||||
specification to production roll out. We also hope to improve
|
||||
throughput by making more efficient use of QA time and reducing idle
|
||||
cycles. We expect people to participate in more than one project
|
||||
depending on their area of expertise and work load. This does not mean
|
||||
people will work on two things at the same time, rather that people
|
||||
come into and leave projects as they free up and not when the project
|
||||
starts or ends.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This new scheme will come into full effect when we finish 1.3
|
||||
although to some degree 1.3 was already setup like this. At the
|
||||
moment, we have 3 major projects, vap/settlments/implied, grid
|
||||
settings and speed work, which are in branches of prod. We will merge
|
||||
these back to prod and then master this week. We have 3 other projects
|
||||
that are in master, the excel API, the server side SDK and the MIC
|
||||
administration and enforcement components. These we hope to stabilize
|
||||
as soon as possible so that we can merge them to prod and deploy them
|
||||
along with the rest. Once we have all this, prod and master should be
|
||||
identical and we will delete prod and operate exclusively out of
|
||||
master. Please avoid committing anything new to master so we can
|
||||
complete this transition as soon as possible.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Let me know if you have any question
|
||||
</p>
|
||||
</div>
|
||||
<div id="postamble" class="status">
|
||||
<p class="author">Author: ppinto</p>
|
||||
<p class="date">Created: 2018-10-08 Mon 18:50</p>
|
||||
<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
We are going to experiment with our development process, getting away
|
||||
from cycles and towards more continuous development. The main idea is
|
||||
to get rid of the prod and QA branches and instead deploy directly
|
||||
from master, which we keep deployment ready at all times. Development
|
||||
occurs on private branches, each branch mapped to a separately
|
||||
deploy-able project. Each of projects will be managed more or less
|
||||
independently and will include specification, development and
|
||||
validation stages. At any particular time we might have anywhere from
|
||||
1 to 6 projects in flight each of which continuously pulls from master,
|
||||
but does not push back to master until the code is ready to be
|
||||
deployed to production. The point at which a project is ready to be
|
||||
deployed then takes roughly the place of the QA phase of our old cycle
|
||||
mode. At this time, we will do a PR, and go through a series of
|
||||
validation steps, to make sure the work meets our standards. This
|
||||
validation includes:
|
||||
|
||||
- Code Reviews
|
||||
- Acceptance Testing (including UI and business tests)
|
||||
- Automated System testing (to be developed)
|
||||
- A mini-qa, focused on the feature, by someone not directly involved
|
||||
in its implementation
|
||||
- If possible, deployment to a beta customer.
|
||||
|
||||
Once these QA gates are passed, we will update the release notes, tag
|
||||
a new version, integrate to master and then ideally immediately deploy
|
||||
to a customer.
|
||||
|
||||
The main goal with this change is to reduce the latency from feature
|
||||
specification to production roll out. We also hope to improve
|
||||
throughput by making more efficient use of QA time and reducing idle
|
||||
cycles. We expect people to participate in more than one project
|
||||
depending on their area of expertise and work load. This does not mean
|
||||
people will work on two things at the same time, rather that people
|
||||
come into and leave projects as they free up and not when the project
|
||||
starts or ends.
|
||||
|
||||
This new scheme will come into full effect when we finish 1.3
|
||||
although to some degree 1.3 was already setup like this. At the
|
||||
moment, we have 3 major projects, vap/settlments/implied, grid
|
||||
settings and speed work, which are in branches of prod. We will merge
|
||||
these back to prod and then master this week. We have 3 other projects
|
||||
that are in master, the excel API, the server side SDK and the MIC
|
||||
administration and enforcement components. These we hope to stabilize
|
||||
as soon as possible so that we can merge them to prod and deploy them
|
||||
along with the rest. Once we have all this, prod and master should be
|
||||
identical and we will delete prod and operate exclusively out of
|
||||
master. Please avoid committing anything new to master so we can
|
||||
complete this transition as soon as possible.
|
||||
|
||||
Let me know if you have any question
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-1
Submodule docker/linux updated: aedfd79e02...8ee9692597.
+1
-1
Submodule externals updated: 83046b27ef...5f21fd13e7.
@@ -1,3 +1,9 @@
|
||||
1.3.5
|
||||
- Change in market data wire format. Old shell will get only direct market data instead of merged which may not match what the servers is driving from.
|
||||
|
||||
1.3.4
|
||||
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
|
||||
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
|
||||
@@ -1,3 +1,50 @@
|
||||
1.3.5
|
||||
- Speed Enhanments
|
||||
- Further speed enhancements
|
||||
- Net Change pricing
|
||||
- The ability to price synthetics based off of net change
|
||||
- Separation of implied and direct market data
|
||||
- Viewing within a market view will now show 3 columns
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
- Ability to configure the market data size in a ladder as the following
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
- Synthetic creation option to choose Merged or Direct
|
||||
- The ability to choose Merged or Direct per leg when creating a synthetic
|
||||
- Volume at price
|
||||
- Within a ladder you can now see a volume at price graph
|
||||
- This is configurable from a ladder's settings as well
|
||||
- - Volume at price
|
||||
- Within a ladder you can now see a volume at price graph
|
||||
- This is configurable from a ladder's settings as well
|
||||
- Customized settings in all windows
|
||||
- All windows now contain a settings parge where grid settings (font color, size, etc) are applied
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
|
||||
|
||||
1.3.4
|
||||
- Significantly reduced client memory use
|
||||
- Improved OrderBook performance
|
||||
- Improved workspace load time
|
||||
|
||||
1.3.3
|
||||
- Reduced server memory usage
|
||||
|
||||
1.3.2
|
||||
- Improved OrderBook performance
|
||||
- Fix some memory leaks when switching workspaces
|
||||
|
||||
1.3.1
|
||||
- Improved server side logging
|
||||
- Fixed leaking of market data subscriptions on market view form closing
|
||||
|
||||
1.3.0
|
||||
- Significantly improved order execution latency
|
||||
- Added latency monitoring tools
|
||||
- Provided core binding controls to system admins so as to reduce latency jitter
|
||||
|
||||
1.1.22
|
||||
- Orderbook: Orders in Error status can now be recreated
|
||||
- Orderbook: Orders containing child orders will now be collapsed by default.
|
||||
|
||||
@@ -11,18 +11,22 @@ namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
readonly string _user_name;
|
||||
public string user_name => _user_name;
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
IReadOnlyDictionary<uint, string>
|
||||
_account_map = new Dictionary<uint, string>(),
|
||||
_user_map = new Dictionary<uint, string>();
|
||||
IDisposable _dispose;
|
||||
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import("username")]string user_name) {
|
||||
_user_name = user_name;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
@@ -52,9 +56,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
|
||||
|
||||
public UserTuple? get_current_user() {
|
||||
foreach (var kv in _user_map) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
class AdminModelWrapper
|
||||
{
|
||||
readonly AdminDataImmutable? _admin_data;
|
||||
readonly string _user_name;
|
||||
readonly List<BTSAccountImmutable> _allowed_accounts;
|
||||
readonly Dictionary<string, string> _allowed_account_dict = new Dictionary<string, string>();
|
||||
public AdminModelWrapper(AdminDataImmutable? admin_data = null, string user_name = null)
|
||||
{
|
||||
_admin_data = admin_data;
|
||||
_user_name = user_name ?? string.Empty;
|
||||
_allowed_accounts = search_admin(u => u.Name == _user_name).Select(x => x.Item1).ToList();
|
||||
foreach(var val in _allowed_accounts)
|
||||
_allowed_account_dict[val.Name] = val.Name;
|
||||
}
|
||||
public IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> get_trading_permissions(UInt32 user_id)
|
||||
{
|
||||
return search_admin(u => u.Id == user_id);
|
||||
}
|
||||
public IEnumerable<BTSAccountImmutable> viewable_accounts()
|
||||
{
|
||||
return _allowed_accounts;
|
||||
}
|
||||
public bool can_view_account(string account)
|
||||
{
|
||||
return _allowed_account_dict.ContainsKey(account) || _user_name.ToLower() == "bts";
|
||||
}
|
||||
|
||||
IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> search_admin(Func<BTSUserImmutable, bool> predicate)
|
||||
{
|
||||
var rv = new List<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>>();
|
||||
if (_admin_data.HasValue)
|
||||
{
|
||||
var users = _admin_data.Value.Users.Where(predicate);
|
||||
if (users != null && users.Count() == 1)
|
||||
{
|
||||
var user = users.ElementAt(0);
|
||||
if (user.HasUserData && user.UserData.HasAccountPermissions)
|
||||
{
|
||||
foreach (var kv in user.UserData.AccountPermissions.AccountPermissions)
|
||||
{
|
||||
var account_id = kv.Key;
|
||||
var accounts = _admin_data.Value.Accounts.Where(a => a.Id == account_id);
|
||||
if (accounts != null && accounts.Count() == 1)
|
||||
{
|
||||
var account = accounts.ElementAt(0);
|
||||
var tp = kv.Value.TradingPermission;
|
||||
var rv_val = new TradingPermissionUnitImmutable(tp);
|
||||
rv.Add(Tuple.Create(account, rv_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.core
|
||||
{
|
||||
|
||||
static class PermissionFilters
|
||||
{
|
||||
public static IObservable<Update<PositionUnitImmutable, int>> PermissionFilter(this IObservable<Update<PositionUnitImmutable, int>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<PositionUnitImmutable, bool>>( observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<PositionUnitImmutable, bool> filter = (z => x.can_view_account(z.Account));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
|
||||
public static IObservable<Update<OrderRecordImmutable, uint>> PermissionFilter(this IObservable<Update<OrderRecordImmutable, uint>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<OrderRecordImmutable, bool>>(observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<OrderRecordImmutable, bool> filter = (z => x.can_view_account(z.account()));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
|
||||
public static IObservable<Update<FillImmutable, string>> PermissionFilter(this IObservable<Update<FillImmutable, string>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<FillImmutable, bool>>(observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<FillImmutable, bool> filter = (z => x.can_view_account(z.Account));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,8 +53,8 @@ namespace bts {
|
||||
}
|
||||
//
|
||||
void handle_bad_icon(string icon_error_desc) {
|
||||
Log.Api.warn($"{nameof(SecurityPickerDaemon)} received a message '{message.title}' with {icon_error_desc}, will use {nameof(Icons.AdminFrozen)}.");
|
||||
icon = Icons.AdminFrozen;
|
||||
Log.Api.warn($"{nameof(SecurityPickerDaemon)} received a message '{message.title}' with {icon_error_desc}, will use {nameof(Icons.Admin)}.");
|
||||
icon = Icons.Admin;
|
||||
}
|
||||
//
|
||||
var cpv = new ContractPickerView(vm);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.api.orderman;
|
||||
using bts.api.persistence;
|
||||
using bts.api.position;
|
||||
@@ -40,5 +41,8 @@ namespace Spark {
|
||||
|
||||
[Export]
|
||||
public PositionService position_service => context.locate<PositionService>();
|
||||
|
||||
[Export]
|
||||
public MarketDataAuxiliaryService md_auxiliary_service => context.locate<MarketDataAuxiliaryService>();
|
||||
}
|
||||
}
|
||||
@@ -16,56 +16,54 @@ namespace mbts.ui {
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
DraggableGeometryKey = nameof(DraggableGeometryKey);
|
||||
|
||||
static Icons() {
|
||||
// TODO: probably do this for all icons, only reason for holding back is the uncertainity of any unexpected impacts
|
||||
AdminFrozen = Admin.frozen();
|
||||
}
|
||||
|
||||
public static Geometry PlayGeometry => Geometry.Parse(_get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
|
||||
|
||||
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
|
||||
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
|
||||
|
||||
|
||||
private static Uri uri(string filename) {
|
||||
return new Uri(base_uri + filename);
|
||||
}
|
||||
|
||||
[Obsolete("Please only add new vector icons using icons.xaml")]
|
||||
private static ImageSource load_image(string filename) {
|
||||
return new BitmapImage(uri(filename));
|
||||
return new BitmapImage(uri(filename)).frozen();
|
||||
}
|
||||
|
||||
private static ImageSource load_resource_and_freeze(string resource_name) {
|
||||
return ((DrawingImage)_resources[resource_name]).frozen();
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public static ImageSource Logo { get { return load_image("logo.ico"); } }
|
||||
public static ImageSource Admin { get { return (DrawingImage)_resources["Admin"]; } }
|
||||
public static ImageSource AdminFrozen { get; }
|
||||
public static ImageSource Analytics { get { return (DrawingImage)_resources["Analytics"]; } }
|
||||
public static ImageSource Sheets { get { return (DrawingImage)_resources["Sheets"]; } }
|
||||
public static ImageSource Trades { get { return (DrawingImage)_resources["Trades"]; } }
|
||||
public static ImageSource Depth { get { return (DrawingImage)_resources["Depth"]; } }
|
||||
public static ImageSource Ladder { get { return (DrawingImage)_resources["Ladder"]; } }
|
||||
public static ImageSource Admin { get { return load_resource_and_freeze("Admin"); } }
|
||||
public static ImageSource Analytics { get { return load_resource_and_freeze("Analytics"); } }
|
||||
public static ImageSource Sheets { get { return load_resource_and_freeze("Sheets"); } }
|
||||
public static ImageSource Trades { get { return load_resource_and_freeze("Trades"); } }
|
||||
public static ImageSource Depth { get { return load_resource_and_freeze("Depth"); } }
|
||||
public static ImageSource Ladder { get { return load_resource_and_freeze("Ladder"); } }
|
||||
public static ImageSource SheetsRed { get { return load_image("SheetsRed.png"); } }
|
||||
public static ImageSource SheetsGreen { get { return load_image("SheetsGreen.png"); } }
|
||||
public static ImageSource Spreads { get { return (DrawingImage)_resources["Spreads"]; } }
|
||||
public static ImageSource Spreads { get { return load_resource_and_freeze("Spreads"); } }
|
||||
public static ImageSource BlueSpreads { get { return load_image("BlueSpread.ico"); } }
|
||||
public static ImageSource Reports { get { return (DrawingImage)_resources["Reports"]; } }
|
||||
public static ImageSource Risk { get { return (DrawingImage)_resources["Risk"]; } }
|
||||
public static ImageSource Eye { get { return (DrawingImage)_resources["Eye"]; } }
|
||||
public static ImageSource Coins { get { return (DrawingImage)_resources["Coins"]; } }
|
||||
public static ImageSource Reports { get { return load_resource_and_freeze("Reports"); } }
|
||||
public static ImageSource Risk { get { return load_resource_and_freeze("Risk"); } }
|
||||
public static ImageSource Eye { get { return load_resource_and_freeze("Eye"); } }
|
||||
public static ImageSource Coins { get { return load_resource_and_freeze("Coins"); } }
|
||||
|
||||
|
||||
public static ImageSource DataCopy { get { return load_image("DataCopy.ico"); } }
|
||||
public static ImageSource Refresh { get { return load_image("Refresh.png"); } }
|
||||
public static ImageSource FavoriteWindow { get { return load_image("FavoriteWindow.png"); } }
|
||||
|
||||
public static ImageSource Browser => (DrawingImage)_resources["Browser"];
|
||||
public static ImageSource Browser => load_resource_and_freeze("Browser");
|
||||
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Cube3d { get { return load_resource_and_freeze("Cube"); } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
public static ImageSource Math { get { return load_resource_and_freeze("Surface"); } }
|
||||
public static ImageSource Abacus { get { return load_image("Abacus.png"); } }
|
||||
|
||||
public static ImageSource Delete { get { return load_image("Delete.png"); } }
|
||||
@@ -77,58 +75,61 @@ namespace mbts.ui {
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
public static ImageSource Trash2 { get { return (DrawingImage)_resources["Trash2"]; } }
|
||||
public static ImageSource Crucible { get { return (DrawingImage)_resources["Crucible"]; } }
|
||||
public static ImageSource Bookmarks { get { return load_resource_and_freeze("Bookmarks"); } }
|
||||
public static ImageSource Window { get { return load_resource_and_freeze("Window"); } }
|
||||
public static ImageSource ActiveWindow { get { return load_resource_and_freeze("ActiveWindow"); } }
|
||||
public static ImageSource Trash { get { return load_resource_and_freeze("Trash"); } }
|
||||
public static ImageSource Trash2 { get { return load_resource_and_freeze("Trash2"); } }
|
||||
public static ImageSource Crucible { get { return load_resource_and_freeze("Crucible"); } }
|
||||
|
||||
public static ImageSource Accounting { get { return (DrawingImage)_resources["Accounting"]; } }
|
||||
public static ImageSource Add { get { return (DrawingImage)_resources["Add"]; } }
|
||||
public static ImageSource AddEncircled { get { return (DrawingImage)_resources["AddEncircled"]; } }
|
||||
public static ImageSource Beaker { get { return (DrawingImage)_resources["Beaker"]; } }
|
||||
public static ImageSource Book { get { return (DrawingImage)_resources["Book"]; } }
|
||||
public static ImageSource Accounting { get { return load_resource_and_freeze("Accounting"); } }
|
||||
public static ImageSource Add { get { return load_resource_and_freeze("Add"); } }
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
|
||||
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
|
||||
|
||||
public static ImageSource Orders => create_icon(OrdersGeometryKey);
|
||||
public static ImageSource AddOrders { get { return (DrawingImage)_resources["AddOrders"]; } }
|
||||
public static ImageSource CancelOrders { get { return (DrawingImage)_resources["CancelOrders"]; } }
|
||||
public static ImageSource CancelBuys { get { return (DrawingImage)_resources["CancelBuys"]; } }
|
||||
public static ImageSource CancelSells { get { return (DrawingImage)_resources["CancelSells"]; } }
|
||||
public static ImageSource CancelSelected { get { return (DrawingImage)_resources["CancelSelected"]; } }
|
||||
|
||||
public static ImageSource LadderCancelOrders { get { return (DrawingImage)_resources["LadderCancelOrders"]; } }
|
||||
public static ImageSource LadderCancelBuys { get { return (DrawingImage)_resources["LadderCancelBuys"]; } }
|
||||
public static ImageSource LadderCancelSells { get { return (DrawingImage)_resources["LadderCancelSells"]; } }
|
||||
public static ImageSource AddOrders { get { return load_resource_and_freeze("AddOrders"); } }
|
||||
public static ImageSource CancelOrders { get { return load_resource_and_freeze("CancelOrders"); } }
|
||||
public static ImageSource CancelBuys { get { return load_resource_and_freeze("CancelBuys"); } }
|
||||
public static ImageSource CancelSells { get { return load_resource_and_freeze("CancelSells"); } }
|
||||
public static ImageSource CancelSelected { get { return load_resource_and_freeze("CancelSelected"); } }
|
||||
|
||||
public static ImageSource Synthetics { get { return (DrawingImage)_resources["Synthetics"]; } }
|
||||
public static ImageSource AddSynthetic { get { return (DrawingImage)_resources["AddSynthetic"]; } }
|
||||
public static ImageSource RemoveSynthetic { get { return (DrawingImage)_resources["RemoveSynthetic"]; } }
|
||||
public static ImageSource ModifySynthetic { get { return (DrawingImage)_resources["ModifySynthetic"]; } }
|
||||
public static ImageSource CloneSynthetic { get { return (DrawingImage)_resources["CloneSynthetic"]; } }
|
||||
public static ImageSource LadderCancelOrders { get { return load_resource_and_freeze("LadderCancelOrders"); } }
|
||||
public static ImageSource LadderCancelBuys { get { return load_resource_and_freeze("LadderCancelBuys"); } }
|
||||
public static ImageSource LadderCancelSells { get { return load_resource_and_freeze("LadderCancelSells"); } }
|
||||
|
||||
public static ImageSource ModifyOrder { get { return (DrawingImage)_resources["ModifyOrder"]; } }
|
||||
public static ImageSource Clock { get { return (DrawingImage)_resources["Clock"]; } }
|
||||
public static ImageSource Defrag { get { return (DrawingImage)_resources["Defrag"]; } }
|
||||
public static ImageSource Display { get { return (DrawingImage)_resources["Display"]; } }
|
||||
public static ImageSource Synthetics { get { return load_resource_and_freeze("Synthetics"); } }
|
||||
public static ImageSource AddSynthetic { get { return load_resource_and_freeze("AddSynthetic"); } }
|
||||
public static ImageSource RemoveSynthetic { get { return load_resource_and_freeze("RemoveSynthetic"); } }
|
||||
public static ImageSource ModifySynthetic { get { return load_resource_and_freeze("ModifySynthetic"); } }
|
||||
public static ImageSource CloneSynthetic { get { return load_resource_and_freeze("CloneSynthetic"); } }
|
||||
|
||||
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
public static ImageSource Draggable => create_icon(DraggableGeometryKey);
|
||||
public static ImageSource Export => (DrawingImage)_resources["Export"];
|
||||
public static ImageSource Filter { get { return (DrawingImage)_resources["Filter"]; } }
|
||||
public static ImageSource FilterActive { get { return (DrawingImage)_resources["FilterActive"]; } }
|
||||
public static ImageSource FilterError { get { return (DrawingImage)_resources["FilterError"]; } }
|
||||
public static ImageSource Export => load_resource_and_freeze("Export");
|
||||
public static ImageSource Filter { get { return load_resource_and_freeze("Filter"); } }
|
||||
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 Import => (DrawingImage)_resources["Import"];
|
||||
public static ImageSource Knight { get { return (DrawingImage)_resources["Knight"]; } }
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Spawn { get { return (DrawingImage)_resources["Spawn"]; } }
|
||||
public static ImageSource Target { get { return (DrawingImage)_resources["Target"]; } }
|
||||
public static ImageSource Tetris { get { return (DrawingImage)_resources["Tetris"]; } }
|
||||
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
|
||||
public static ImageSource Target { get { return load_resource_and_freeze("Target"); } }
|
||||
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
|
||||
public static ImageSource Tools => create_icon(ToolsGeometryKey);
|
||||
public static ImageSource ViewSettings => (DrawingImage)_resources["ViewSettings"];
|
||||
public static ImageSource Wrench { get { return (DrawingImage)_resources["Wrench"]; } }
|
||||
public static ImageSource RightArrow { get { return ((DrawingImage)_resources["RightArrow"]); } }
|
||||
public static ImageSource DownArrow { get { return ((DrawingImage)_resources["DownArrow"]); } }
|
||||
public static ImageSource Support => (DrawingImage)_resources["SupportIcon"];
|
||||
public static ImageSource ViewSettings => load_resource_and_freeze("ViewSettings");
|
||||
public static ImageSource Wrench { get { return load_resource_and_freeze("Wrench"); } }
|
||||
public static ImageSource RightArrow { get { return (load_resource_and_freeze("RightArrow")); } }
|
||||
public static ImageSource DownArrow { get { return (load_resource_and_freeze("DownArrow")); } }
|
||||
public static ImageSource Factory { get { return (load_resource_and_freeze("Factory")); } }
|
||||
public static ImageSource DotsVertical { get { return (load_resource_and_freeze("DotsVertical")); } }
|
||||
public static ImageSource HandPointingOverDocument => load_resource_and_freeze("HandPointingOverDocument");
|
||||
public static ImageSource Support => load_resource_and_freeze("SupportIcon");␍
|
||||
|
||||
public static ImageSource create_icon(string geometric_path_key) =>
|
||||
create_icon(geometric_path_key, Brushes.Black);
|
||||
|
||||
@@ -813,8 +813,48 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="Factory" x:Shared="False">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M21.832001,24.424L21.832001,27.912998 26.218018,27.912998 26.218018,24.424z M13.757019,24.424L13.757019,27.912998 18.143005,27.912998 18.143005,24.424z M5.7820129,24.424L5.7820129,27.912998 10.167999,27.912998 10.167999,24.424z M1.8950195,6.3799964L3.8880005,6.3799964 4.1870117,17.745001 8.1750183,17.745001 8.1750183,13.956 16.050018,17.745001 16.149994,17.745001 16.149994,13.956 24.024994,17.745001 24.125,17.745001 24.125,13.956 32,17.745001 31.901001,17.745001 31.901001,31.103001 0,31.103001 0,17.745001 1.5950012,17.745001z M8.5740051,1.3959956C8.5740051,1.3959958 9.1719971,3.2899927 6.6800232,3.5889963 6.6800232,3.5889963 5.085022,3.6879953 4.2870178,4.4859993 4.2870178,4.4859993 3.9880066,4.7849952 3.8880005,4.4859993 3.8880005,4.4859993 3.9880066,3.0899958 6.480011,2.6919931 6.5800171,2.7909998 8.3740234,2.6919931 8.5740051,1.3959956z M5.5830078,0C5.5830078,-1.4470834E-07 6.8789978,1.4949948 4.5859985,2.6919931 4.5859985,2.6919931 3.1900024,3.2899927 2.6920166,4.3859931 2.6920166,4.3859931 2.5920105,4.7849952 2.2929993,4.4859993 2.2929993,4.4859993 1.8950195,3.1899942 4.0880127,1.8939967 4.1870117,1.9939953 5.882019,1.2959973 5.5830078,0z"
|
||||
/>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<!-- Pin
|
||||
<DrawingImage x:Key="DotsVertical" x:Shared="False">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"
|
||||
/>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
|
||||
<DrawingImage x:Key="HandPointingOverDocument" x:Shared="False">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M17.740002,11.999007C17.254002,11.999007,16.859001,12.394008,16.859001,12.879008L16.859001,24.458791 16.860127,24.479386C16.861003,24.607261 16.813003,24.735511 16.716002,24.834012 16.667502,24.883012 16.611378,24.91995 16.551394,24.944762L16.521562,24.955436 16.507633,24.960536 16.506465,24.960837 16.50574,24.961097 16.49194,24.964574 16.459728,24.972863C16.42719,24.979527,16.393502,24.983027,16.359002,24.983027L16.316763,24.980893 16.315716,24.980851 16.307903,24.980445C16.223895,24.971904,16.146014,24.942538,16.07951,24.897597L16.067752,24.887895 16.047276,24.873078 16.015234,24.844558 16.005502,24.836528 16.001236,24.831357 13.737002,22.600011C13.267003,22.134011 12.502003,22.135011 12.031003,22.600011 11.805003,22.82301 11.680003,23.120011 11.680003,23.436011 11.680003,23.751012 11.805003,24.048011 12.031003,24.272012L16.842003,29.031013C19.311003,31.189013 21.546002,31.119014 22.656003,30.886014 23.939002,30.614013 25.015002,29.846013 25.464002,28.880013 25.857001,28.036012 26.301002,27.080012 26.263001,26.195012 26.262002,26.178012 26.268003,26.074012 26.269002,26.056012L26.268003,20.974011C26.268003,20.55601 25.922002,20.215011 25.496001,20.215011 25.335502,20.215011 25.239001,20.246511 25.179877,20.283135L25.144,20.309762 25.144,21.215013C25.144,21.491012 24.919999,21.715013 24.644,21.715013 24.367999,21.715013 24.144,21.491012 24.144,21.215013L24.144,19.278017C24.144,18.793018 23.749,18.399019 23.263,18.399019 22.808312,18.399019 22.432727,18.745308 22.387555,19.18822L22.383004,19.277944 22.383004,19.716007C22.383004,19.992006 22.159003,20.216004 21.883002,20.216004 21.607003,20.216004 21.383002,19.992006 21.383002,19.716007L21.383002,19.278054 21.383002,18.504013C21.383002,18.018016 20.988002,17.623017 20.502002,17.623017 20.016002,17.623017 19.621001,18.018016 19.621001,18.504013L19.621001,20.24402C19.621001,20.52102 19.397001,20.74402 19.121001,20.74402 18.844001,20.74402 18.621001,20.52102 18.621001,20.24402L18.621001,18.504035 18.621001,12.879008C18.621001,12.394008,18.226001,11.999007,17.740002,11.999007z M17.740002,10.999005C18.777001,10.999005,19.621001,11.843007,19.621001,12.879008L19.621001,16.842839 19.687315,16.808824C19.933955,16.689783 20.210345,16.623022 20.502002,16.623022 21.247346,16.623022 21.892987,17.059031 22.197202,17.689331L22.211675,17.721288 22.212884,17.720384C22.513,17.517568 22.874499,17.399021 23.263,17.399021 24.267594,17.399021 25.091062,18.190155 25.141547,19.181478L25.143222,19.247425 25.174827,19.241074C25.276639,19.223799 25.383876,19.21501 25.496001,19.21501 26.473002,19.21501 27.268003,20.00401 27.268003,20.974011L27.268003,26.087012C27.268003,26.098012 27.262002,26.194013 27.262002,26.205012 27.311002,27.281013 26.790002,28.401013 26.371001,29.302012 25.783002,30.566012 24.472001,31.523013 22.863002,31.864013 22.513001,31.938014 22.067002,31.999013 21.547002,31.999013 20.149003,31.999013 18.216002,31.561013 16.161002,29.763013L11.328003,24.983012C10.910003,24.570012 10.680003,24.020011 10.680003,23.436011 10.680003,22.85101 10.910003,22.302011 11.328003,21.88901 12.187003,21.040011 13.583003,21.041011 14.440002,21.88901L15.859002,23.287391 15.859002,12.879008C15.859002,11.843007,16.703002,10.999005,17.740002,10.999005z M6.8659983,4.0500388L19.083997,4.0500388C20.668996,4.0500388,21.957997,5.3460296,21.957997,6.9390478L21.957997,14.653042C21.957997,14.93005 21.734998,15.153042 21.457997,15.153042 21.181996,15.153042 20.957997,14.93005 20.957997,14.653042L20.957997,6.9390478C20.957997,5.8980318,20.116998,5.0500394,19.083997,5.0500394L6.8659983,5.0500394C5.8319979,5.0500394,4.9909983,5.8980318,4.9909984,6.9390478L4.9909984,26.116061C4.9909983,27.157046,5.8319979,28.00504,6.8659983,28.00504L11.696996,28.00504C11.972997,28.00504 12.196996,28.229039 12.196996,28.50504 12.196996,28.781041 11.972997,29.00504 11.696996,29.00504L6.8659983,29.00504C5.2809954,29.00504,3.9909983,27.70905,3.9909983,26.116061L3.9909983,6.9390478C3.9909983,5.3460296,5.2809954,4.0500388,6.8659983,4.0500388z M6.4360008,0L17.467001,0C17.743001,0 17.967001,0.22300029 17.967001,0.50000024 17.967001,0.77600098 17.743001,1.0000012 17.467001,1.0000012L6.4360008,1.0000012C3.4380004,1.0000012,1.0000002,3.4210042,1.0000002,6.3970079L1.0000002,20.617026C1.0000002,20.893025 0.77600026,21.117026 0.50000048,21.117026 0.22400022,21.117026 2.3841858E-07,20.893025 0,20.617026L0,6.3970079C2.3841858E-07,2.8700035,2.8870004,0,6.4360008,0z"
|
||||
/>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
|
||||
<!-- Pin
|
||||
<Path Data="F1M3,11L3,7 0,7 0,6 1,6 1,1 1,0 6,0 6,1 6,6 7,6 7,7 4,7 4,11 3,11z M2,1L2,6 4,6 4,1 2,1z" />
|
||||
Unpin
|
||||
<Path Data="F1M0,3L4,3 4,0 5,0 5,1 10,1 11,1 11,6 10,6 5,6 5,7 4,7 4,4 0,4 0,3z M10,2L5,2 5,4 10,4 10,2z" />
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using bts.api.core;
|
||||
using bts.api.md.auxiliary;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Concurrency;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class MarketDataAuxiliaryModel : IDisposable {
|
||||
readonly MarketDataAuxiliaryService _service;
|
||||
readonly TableUpdateDemuxer<VolumeAtPriceLevelImmutable, int, uint> _security_orders_demuxer;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
MarketDataAuxiliaryModel(MarketDataAuxiliaryService service) {
|
||||
_service = service;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
volume_at_price = service.subscribei_volume_at_price().Naggle(interval, scheduler).Table().Cache();
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<VolumeAtPriceLevelImmutable, int, uint>(o => o.RecordId, v => v.SecurityId);
|
||||
_dispose = volume_at_price.AddRemoveOnly().Subscribe(_security_orders_demuxer);
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<VolumeAtPriceLevelImmutable, int>> volume_at_price { get; }
|
||||
|
||||
public IObservable<ITableUpdateTransform<VolumeAtPriceLevelImmutable, int>> security_volume_at_price(uint security) =>
|
||||
_security_orders_demuxer.subscribe(security);
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System.Collections.Generic;
|
||||
@@ -17,9 +18,9 @@ namespace bts.shell
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, OrdersModel orders_model, [Import("username")]string user_name) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, orders_model);
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
TimeAndSalesWindowFactory _tsf;
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace mbts.spark {
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<uint> _bid_size, _ask_size;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
@@ -35,6 +34,9 @@ namespace mbts.spark {
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
uint _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
@@ -46,8 +48,6 @@ namespace mbts.spark {
|
||||
_position = new CellProperty<int>(0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_bid_size = backing<uint>(nameof(bid_size), 0);
|
||||
_ask_size = backing<uint>(nameof(ask_size), 0);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
@@ -70,34 +70,25 @@ namespace mbts.spark {
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
bid_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidSize));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskSize));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
|
||||
_position.value_set += position_value_set;
|
||||
_inside_bids.value_set += inside_bids_value_set;
|
||||
_inside_asks.value_set += inside_asks_value_set;
|
||||
_day_change.Changed += day_change_Changed;
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
Disposable.Create(() => {
|
||||
_position.value_set -= position_value_set;
|
||||
_inside_bids.value_set -= inside_bids_value_set;
|
||||
_inside_asks.value_set -= inside_asks_value_set;
|
||||
_day_change.Changed -= day_change_Changed;
|
||||
_animation.AnimationsChanged -= on_animations_changed;
|
||||
})
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
|
||||
void position_value_set((int oldval, int newval) v) => _animation.animate_position(v.newval > v.oldval);
|
||||
void inside_bids_value_set((bool oldval, bool newval) v) => _animation.animate_working_bids_inside(v.newval);
|
||||
void inside_asks_value_set((bool oldval, bool newval) v) => _animation.animate_working_asks_inside(v.newval);
|
||||
void day_change_Changed(double oldval, double newval) => _animation.animate_day_change(newval > oldval);
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
@@ -109,9 +100,15 @@ namespace mbts.spark {
|
||||
public double last_price => _last_price.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_size => _bid_size.Value;
|
||||
public uint bid_direct_size => _bid_direct_size;
|
||||
public uint bid_implied_size => _bid_merged_size - _bid_direct_size;
|
||||
public uint bid_merged_size => _bid_merged_size;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_size => _ask_size.Value;
|
||||
public uint ask_direct_size => _ask_direct_size;
|
||||
public uint ask_implied_size => _ask_merged_size - _ask_direct_size;
|
||||
public uint ask_merged_size => _ask_merged_size;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
@@ -125,10 +122,14 @@ namespace mbts.spark {
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_size_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_size_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
|
||||
@@ -139,51 +140,36 @@ namespace mbts.spark {
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of IDisposable 'update jobs.' Each item represents a job that will be executed when disposed; all jobs should be executed on the dispatcher thread.
|
||||
/// </summary>
|
||||
// Note: not guarenteed to be on the dispatcher thread, so all view-notifying actions should be wrapped in a IDisposable.
|
||||
// Having said that, perform as much work as possible locally.
|
||||
public List<IDisposable> refresh() {
|
||||
var update_jobs = new List<IDisposable>();
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
prop_job(_bid_price, b.price);
|
||||
prop_job(_bid_size, b.size);
|
||||
prop_job(_ask_price, a.price);
|
||||
prop_job(_ask_size, a.size);
|
||||
cell_prop_job(_inside_bids, b.price >= _working_best_bid.value);
|
||||
cell_prop_job(_inside_asks, a.price >= _working_best_ask.value);
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, ref _bid_direct_size, ref _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, ref _ask_direct_size, ref _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_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);
|
||||
}
|
||||
double new_last_price = last_price;
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out ulong v)) {
|
||||
new_last_price = ps.price;
|
||||
prop_job(_last_price, new_last_price);
|
||||
cell_prop_job(_last_size, ps.size);
|
||||
cell_prop_job(_volume, v);
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
double new_settlement = md.md.settlement_price;
|
||||
prop_job(_settlement, new_settlement);
|
||||
prop_job(_session_low, md.md.session_low);
|
||||
prop_job(_session_high, md.md.session_high);
|
||||
prop_job(_day_change, new_last_price - new_settlement);
|
||||
_settlement.set(md.md.settlement_price);
|
||||
_session_low.set(md.md.session_low);
|
||||
_session_high.set(md.md.session_high);
|
||||
_day_change.set(last_price - settlement);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
if (dirty) {
|
||||
update_jobs.Add(Disposable.Create(() => {
|
||||
invalidated?.Invoke();
|
||||
}));
|
||||
}
|
||||
return update_jobs;
|
||||
|
||||
void prop_job<T>(Property<T> p, T newval) {
|
||||
var uj = p.set_as_job(newval);
|
||||
if (uj != null)
|
||||
update_jobs.Add(uj);
|
||||
}
|
||||
void cell_prop_job<T>(CellProperty<T> p, T newval) {
|
||||
var uj = p.on_next_distinct_as_job(newval, ref dirty);
|
||||
if (uj != null)
|
||||
update_jobs.Add(uj);
|
||||
void refresh_size(ref NativePriceSize nps, ref uint direct, ref uint merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged || nps.direct_size != direct) {
|
||||
merged = nps.size;
|
||||
direct = nps.direct_size;
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,17 +208,33 @@ namespace mbts.spark {
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidSize:
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
order.Side = Side.Buy;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskSize:
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
order.Side = Side.Buy;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_size));
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
order.Side = Side.Sell;
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using bts.api;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
@@ -17,68 +17,82 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD>, IPrepareUpdateForDispatcher {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state)
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus))) { }
|
||||
|
||||
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, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable)
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string size_col_header = "Qty", working_col_header = "Working";
|
||||
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;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), 64, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true);
|
||||
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);
|
||||
add_hl_size_col(Columns.BidSize, size_col_header, nameof(Row.bid_size), nameof(Row.bid_size_click), bid_size_filter, BTSColors.MarketData.bid);
|
||||
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_brush);
|
||||
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_brush);
|
||||
add_hl_size_col(Columns.AskSize, size_col_header, nameof(Row.ask_size), nameof(Row.ask_size_click), ask_size_filter, BTSColors.MarketData.ask);
|
||||
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);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Price, 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.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax, 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_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_brush, 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_brush, 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);
|
||||
//
|
||||
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) =>
|
||||
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,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush background) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price, foreground, background, DefaultSelectionHighlight);
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush 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, DefaultSelectionHighlight, h_alignment);
|
||||
|
||||
void add_hl_size_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(col_key, header, new Binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.UShortMinMax, DefaultColumnWidths.Size, foreground, null, null);
|
||||
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.UShortMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null);
|
||||
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, null, h_alignment);
|
||||
|
||||
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, Brush background, Brush background_overlay) =>
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Brush background, Brush background_overlay, 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 = background,
|
||||
background_overlay = background_overlay,
|
||||
Width = DefaultColumnWidths.Price
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
@@ -88,17 +102,41 @@ namespace mbts.spark {
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// 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 },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.BidImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.BidDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.BidMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.AskImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.AskDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.AskMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ 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()) {
|
||||
rows_view.SetSortingCriteria(s.sorting_criteria);
|
||||
var filters = s.filters;
|
||||
if (filters != null) {
|
||||
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_size_filter.load_state(filters.bid_size);
|
||||
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_size_filter.load_state(filters.ask_size);
|
||||
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_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
@@ -111,22 +149,40 @@ namespace mbts.spark {
|
||||
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_size_filter, ask_price_filter, ask_size_filter, last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
securities_connectable.Connect()
|
||||
securities_connectable.Connect(),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
#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_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_size, nameof(Row.bid_size));
|
||||
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_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_size, nameof(Row.ask_size));
|
||||
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, 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));
|
||||
@@ -143,9 +199,13 @@ namespace mbts.spark {
|
||||
return new FilterStates {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_size = bid_size_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_size = ask_size_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
@@ -161,25 +221,6 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IDisposable prepare_update() {
|
||||
ConcurrentBag<List<IDisposable>> to_update = new ConcurrentBag<List<IDisposable>>();
|
||||
try {
|
||||
Parallel.For(0, _row_vms.Count, i => {
|
||||
var r = _row_vms[i];
|
||||
var update_jobs = r.refresh();
|
||||
if (update_jobs.Count > 0)
|
||||
to_update.Add(update_jobs);
|
||||
});
|
||||
} catch (IndexOutOfRangeException) {
|
||||
// since rows (securities) will only be added/removed through rare user intervention,
|
||||
// it is worth catching the occassional exception in exchange for lock-free code.
|
||||
// if a row misses an update, or gets updated more than once, that's okay too;
|
||||
// in 100ms it will tick again, so the data should never be more than a few 100 ms old, and rarely older than 100.
|
||||
}
|
||||
// return null, rather than an empty IDisposable; this signals to the UpdateMaster that it may skip the call to Dispatcher.InvokeAsync() as there is nothing to dispose.
|
||||
return to_update.Count == 0 ? null : Disposable.Create(() => to_update.SelectMany(_ => _).DisposeAll());
|
||||
}
|
||||
|
||||
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); }
|
||||
@@ -188,4 +229,4 @@ namespace mbts.spark {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
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}"/>
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidSize, Ask, AskSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
|
||||
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -14,20 +17,20 @@ using System.Windows;
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly DispatcherUpdateMaster _dispatcher_update_master;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
MarketViewVM _vm;
|
||||
MarketViewView _view;
|
||||
MarketViewWindowFactory.StartupData _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _resources;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, DispatcherUpdateMaster dispatcher_update_master, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state)
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
_dispatcher_update_master = dispatcher_update_master;
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
}
|
||||
|
||||
@@ -36,15 +39,21 @@ namespace mbts.spark {
|
||||
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.rows_view.GetSortingCriteria(), _vm.get_filter_states());
|
||||
public override double MinWidth => 1035 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
public override MarketViewWindowFactory.StartupData SaveState =>
|
||||
_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.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
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);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
_commands = new List<SparkCommand> {
|
||||
@@ -57,18 +66,34 @@ namespace mbts.spark {
|
||||
),
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria))
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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())),
|
||||
this.observe_title()
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
_resources = Disposable.Create(
|
||||
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(
|
||||
c => _state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states()))),
|
||||
_dispatcher_update_master.attach(_vm));
|
||||
_subscriptions = _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())));
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _resources);
|
||||
Disposable.dispose(ref _subscriptions);
|
||||
_vm.Dispose();
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,47 @@
|
||||
using bts.api;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace bts.shell {
|
||||
class MarketViewWindowFactory : ShellWindowFactory<MarketViewWindowFactory.StartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly DispatcherUpdateMaster _dispatcher_update_master = new DispatcherUpdateMaster("MarketView DispatcherUpdateMaster");
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
Task.FromResult(_create(state));
|
||||
|
||||
void _show(SecurityCriteria criteria) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(new StartupData(criteria, new List<SortingCriteria> { new SortingCriteria(nameof(MarketViewVM.Row.security), true) }, null)));
|
||||
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
|
||||
|
||||
IShellWindow _create(StartupData state) => new MarketViewWindow(this, _dispatcher_update_master, _bus, _orders_model, state);
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.market_view_grid,
|
||||
set: (us, gs) => us.with(provided_market_view_gridsettings: gs));
|
||||
return new MarketViewWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
@@ -35,20 +50,89 @@ namespace bts.shell {
|
||||
[ProtoMember(1)]
|
||||
public readonly SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public readonly List<SortingCriteria> sorting_criteria;
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public readonly FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
StartupData() { }
|
||||
public StartupData() { }
|
||||
|
||||
public StartupData(SecurityCriteria security_criteria)
|
||||
: this (security_criteria, new List<SortingCriteria>(), null) { }
|
||||
: this (security_criteria, new FilterStates()) { }
|
||||
|
||||
public StartupData(SecurityCriteria security_criteria, List<SortingCriteria> sorting_criteria, FilterStates filters) {
|
||||
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sort = null) {
|
||||
this.security_criteria = security_criteria;
|
||||
this.sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
_sorting_criteria = sort;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public static StartupData Default(SecurityCriteria security_criteria) =>
|
||||
new StartupData(security_criteria, new FilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings volume => new GridColumnSettings(col_id(Columns.Volume), DefaultColumnHeaders.Volume, width: DefaultColumnWidths.Volume);
|
||||
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
|
||||
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings working_bids => new GridColumnSettings(col_id(Columns.WorkingBids), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid_implied_size => new GridColumnSettings(col_id(Columns.BidImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid_direct_size => new GridColumnSettings(col_id(Columns.BidDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid_merged_size => new GridColumnSettings(col_id(Columns.BidMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_merged_size => new GridColumnSettings(col_id(Columns.AskMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_direct_size => new GridColumnSettings(col_id(Columns.AskDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_implied_size => new GridColumnSettings(col_id(Columns.AskImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings working_asks => new GridColumnSettings(col_id(Columns.WorkingAsks), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings settlement => new GridColumnSettings(col_id(Columns.Settlement), DefaultColumnHeaders.Settlement, width: DefaultColumnWidths.Settlement, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_change => new GridColumnSettings(col_id(Columns.DayChange), DefaultColumnHeaders.DayChange, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings session_low => new GridColumnSettings(col_id(Columns.SessionLow), DefaultColumnHeaders.SessionLow, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings session_high => new GridColumnSettings(col_id(Columns.SessionHigh), DefaultColumnHeaders.SessionHigh, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
|
||||
|
||||
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(security.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return volume;
|
||||
yield return last;
|
||||
yield return last_size;
|
||||
yield return position;
|
||||
yield return working_bids;
|
||||
yield return bid_implied_size;
|
||||
yield return bid_direct_size;
|
||||
yield return bid_merged_size;
|
||||
yield return bid;
|
||||
yield return ask;
|
||||
yield return ask_merged_size;
|
||||
yield return ask_direct_size;
|
||||
yield return ask_implied_size;
|
||||
yield return working_asks;
|
||||
yield return settlement;
|
||||
yield return day_change;
|
||||
yield return session_low;
|
||||
yield return session_high;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -58,11 +142,11 @@ namespace bts.shell {
|
||||
[ProtoMember(2)]
|
||||
public DoubleMinMaxState bid_price;
|
||||
[ProtoMember(3)]
|
||||
public UIntMinMaxState bid_size;
|
||||
public UIntMinMaxState bid_merged_size;
|
||||
[ProtoMember(4)]
|
||||
public DoubleMinMaxState ask_price;
|
||||
[ProtoMember(5)]
|
||||
public UIntMinMaxState ask_size;
|
||||
public UIntMinMaxState ask_merged_size;
|
||||
[ProtoMember(6)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(7)]
|
||||
@@ -85,6 +169,14 @@ namespace bts.shell {
|
||||
public ULongMinMaxState volume;
|
||||
[ProtoMember(16)]
|
||||
public string security_key;
|
||||
[ProtoMember(17)]
|
||||
public UIntMinMaxState bid_direct_size;
|
||||
[ProtoMember(18)]
|
||||
public UIntMinMaxState bid_implied_size;
|
||||
[ProtoMember(19)]
|
||||
public UIntMinMaxState ask_direct_size;
|
||||
[ProtoMember(20)]
|
||||
public UIntMinMaxState ask_implied_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,14 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
class TimeAndSalesModel : IDisposable {
|
||||
internal readonly SecurityCriteria security_criteria;
|
||||
|
||||
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
|
||||
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
|
||||
IDisposable _dispose;
|
||||
|
||||
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
|
||||
this.security_criteria = security_criteria;
|
||||
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
|
||||
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
|
||||
var copy = curr_channels;
|
||||
|
||||
@@ -3,7 +3,6 @@ using bts.co2;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
@@ -15,17 +14,25 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using mbts.persist;
|
||||
using mbts.ui.settings;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
|
||||
readonly IBus _bus;
|
||||
readonly TimeAndSalesModel _model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public TimeAndSalesVM(IBus bus, TimeAndSalesModel model, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateBuffer(model.trades, 1000), sorting_criteria) {
|
||||
|
||||
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
|
||||
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
if (filters != null) {
|
||||
if (startup_data.filters is FilterStates filters) {
|
||||
time_filter.load_state(filters.time);
|
||||
security_filter.load_state(filters.security);
|
||||
price_filter.load_state(filters.price);
|
||||
@@ -35,33 +42,49 @@ namespace mbts.spark {
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
// Columns
|
||||
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DIYGrid.DefaultColumnWidths.Aggressor, aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DefaultColumnWidths.TradeKey, nameof(Row.trade_key), trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new TimeAndSalesColumn(
|
||||
r => r[col_key]
|
||||
col_key
|
||||
, (r) => _get_aggressor_brush(r)
|
||||
, filter
|
||||
, filter_template
|
||||
)
|
||||
{ Header = header, Width = width, horizontal_alignment = h_alignment }
|
||||
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||
// if the collection is not populated, the column settings will not be applied
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
_resources.AddRange(
|
||||
show_ladder,
|
||||
DataGridFilterHelper.hook_filters(
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter
|
||||
)
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
|
||||
}
|
||||
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
|
||||
|
||||
private Brush _get_aggressor_brush(Row r) {
|
||||
BTSColor c;
|
||||
@@ -82,7 +105,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> trade_key_filter { get; } = new RegexFilterVM<Row>(r => r.trade_key.ToString(), nameof(Row.trade_key));
|
||||
|
||||
public FilterStates get_filter_states() =>
|
||||
FilterStates _get_filter_states() =>
|
||||
new FilterStates {
|
||||
time = time_filter.get_state(),
|
||||
security = security_filter.get_state(),
|
||||
@@ -121,7 +144,7 @@ namespace mbts.spark {
|
||||
|
||||
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
|
||||
_trade_key = trade.Key;
|
||||
time = trade.Time.lldatetime();
|
||||
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
|
||||
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
|
||||
size = trade.Size;
|
||||
aggressor_side = trade.AggressorSide;
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<c:DIYGrid Columns="{Binding columns}" Rows="{Binding rows_view}" CurrentRow="{Binding current_row}">
|
||||
<c:DIYGrid Columns="{Binding columns}"
|
||||
Rows="{Binding rows_view}" CurrentRow="{Binding current_row}"
|
||||
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}"/>
|
||||
|
||||
@@ -3,8 +3,11 @@ using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -20,6 +23,7 @@ namespace mbts.spark {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
SingleValuePump<TimeAndSalesVM> _vm;
|
||||
TimeAndSalesModel _model;
|
||||
StartupData _state;
|
||||
@@ -27,29 +31,25 @@ namespace mbts.spark {
|
||||
SparkCommand[] _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon)
|
||||
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
|
||||
Log.assert(state != null);
|
||||
_consumer = consumer;
|
||||
_factory = factory;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override UIElement Content => _view;
|
||||
public override double MinWidth => DefaultColumnWidths.Security
|
||||
+ DefaultColumnWidths.Time
|
||||
+ DefaultColumnWidths.Price
|
||||
+ DefaultColumnWidths.Size
|
||||
+ DefaultColumnWidths.Aggressor
|
||||
+ DefaultColumnWidths.TradeKey
|
||||
+ DefaultColumnWidths.SecurityKey
|
||||
public override double MinWidth => StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.security_criteria, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
|
||||
public override void Dispose() =>
|
||||
_dispose_resources();
|
||||
@@ -66,7 +66,25 @@ namespace mbts.spark {
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria))
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
s => s.time_and_sales_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
_subscriptions = Disposable.Create(
|
||||
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
|
||||
@@ -79,7 +97,7 @@ namespace mbts.spark {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
});
|
||||
void update_criteria(SecurityCriteria c) {
|
||||
_state = new StartupData(c, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
|
||||
_state = _vm.value.create().with(c);
|
||||
_initialize();
|
||||
}
|
||||
}
|
||||
@@ -98,7 +116,7 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _model);
|
||||
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
|
||||
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
|
||||
_vm.value = new TimeAndSalesVM(_bus, _model, _state.sorting_criteria, _state.filters);
|
||||
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
|
||||
if (_view == null)
|
||||
_view = new TimeAndSalesView();
|
||||
_view.DataContext = _vm.value;
|
||||
|
||||
@@ -2,24 +2,31 @@
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.TimeAndSalesView;
|
||||
|
||||
namespace bts.shell {
|
||||
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly MDConsumer _consumer;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer) : base(module, "TimeAndSales") {
|
||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
|
||||
_bus = bus;
|
||||
_consumer = consumer;
|
||||
_settings_hub = settings_hub;
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
@@ -27,8 +34,11 @@ namespace bts.shell {
|
||||
);
|
||||
}
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon));
|
||||
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
|
||||
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
|
||||
|
||||
public async void show(SecurityCriteria criteria) {
|
||||
var w = await load(new StartupData(criteria));
|
||||
@@ -40,19 +50,60 @@ namespace bts.shell {
|
||||
[ProtoMember(1)]
|
||||
public readonly SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public readonly List<SortingCriteria> sorting_criteria;
|
||||
List<SortingCriteria> _sorting_criterion;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
StartupData() { }//for ptorobuf
|
||||
public StartupData() { }
|
||||
|
||||
public StartupData(SecurityCriteria security_criteria)
|
||||
: this (security_criteria, new List<SortingCriteria>(), null) { }
|
||||
public StartupData(SecurityCriteria security_criteria) :
|
||||
this(security_criteria, new FilterStates(), null, null) { }
|
||||
|
||||
public StartupData(SecurityCriteria security_criteria, List<SortingCriteria> sorting_criteria, FilterStates filters) {
|
||||
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
|
||||
this.security_criteria = security_criteria;
|
||||
this.sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
_sorting_criterion = sorting_criterion;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public static StartupData Default(SecurityCriteria security_criteria) =>
|
||||
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public StartupData with(SecurityCriteria security_criteria) =>
|
||||
new StartupData(security_criteria, filters, grid);
|
||||
|
||||
public static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
|
||||
public static GridColumnSettings trade_key => new GridColumnSettings(col_id(Columns.TradeKey), DefaultColumnHeaders.TradeKey, width: DefaultColumnWidths.TradeKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return time;
|
||||
yield return price;
|
||||
yield return size;
|
||||
yield return aggressor;
|
||||
yield return trade_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace bts.ui {
|
||||
|
||||
_bus = bus;
|
||||
}
|
||||
public ImageSource icon => Icons.AdminFrozen;
|
||||
public ImageSource icon => Icons.Admin;
|
||||
|
||||
public string name => "Settings";
|
||||
|
||||
|
||||
@@ -46,27 +46,22 @@
|
||||
Offset="{StaticResource RowHeight}"
|
||||
RowHeaderWidth="{StaticResource RowHeaderWidth}"
|
||||
/>
|
||||
<usp:DataGridParameterCellTemplateConverter x:Key="ParameterCellTemplateConverter">
|
||||
<DataTemplate x:Key="SimpleValueTextTemplate">
|
||||
<TextBlock Text="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter
|
||||
x:Key="ParameterCellTemplateConverter"
|
||||
DoubleTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
IntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
UIntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
>
|
||||
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox HorizontalAlignment="Left" IsChecked="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.IntTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.IntTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.UIntTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
@@ -117,6 +112,16 @@
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.MarketDataCalculationTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.LegMarketDataCalculation}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.MarketDataCalculationTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding enabled}">
|
||||
|
||||
@@ -302,6 +302,8 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate IntTemplate { get; set; }
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
public DataTemplate MarketDataTypeTemplate { get; set; }
|
||||
public DataTemplate MarketDataCalculationTemplate { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
Type t = (Type)value;
|
||||
@@ -310,6 +312,8 @@ namespace mbts.ui.spark {
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.LegMarketDataCalculation)) return MarketDataCalculationTemplate;
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
@@ -18,8 +19,8 @@ namespace bts.shell
|
||||
LatencyReportWindowFactory _lrwf;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderShellModule(IBus bus, OrdersModel orders_model) {
|
||||
_smwf = new SyntheticManagementWindowFactory(this, bus, orders_model);
|
||||
public SpreaderShellModule(IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) {
|
||||
_smwf = new SyntheticManagementWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_lrwf = new LatencyReportWindowFactory(this, bus);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<UserControl x:Class="mbts.ui.spark.SyntheticManagementView" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls">
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.SyntheticManagementView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -10,71 +14,31 @@
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="grid" BorderThickness="0" IsReadOnly="True" CanUserSortColumns="True"
|
||||
GridLinesVisibility="None" ItemsSource="{Binding rows_view}"
|
||||
CurrentItem="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="u:AttachedCommands.DoubleClick" Value="{Binding show_ladder}"/>
|
||||
<Setter Property="u:AttachedCommands.Enter" Value="{Binding show_ladder}"/>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<!--<DataGridTextColumn
|
||||
Header="Create Time" Binding="{Binding create_time}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.create_time_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.create_time_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource TimeFilterTemplate}"
|
||||
/>-->
|
||||
<DataGridTextColumn
|
||||
Header="Name" Binding="{Binding name}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.name_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.name_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.name_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn
|
||||
Header="Description" Binding="{Binding description}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.description_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.description_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.description_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn
|
||||
Header="# Legs" Binding="{Binding n_legs}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.n_legs_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.n_legs_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.n_legs_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource IntMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTemplateColumn
|
||||
Header="# Working Orders"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.n_working_orders_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.n_working_orders_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.n_working_orders_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource IntMinMaxTemplate}"
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
>
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
<Hyperlink Command="{Binding show_order_book}">
|
||||
<TextBlock Text="{Binding n_working_orders}"/>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn
|
||||
Header="Security Key" Binding="{Binding security_key}" Width="*"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.security_key_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_key_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_key_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
</DataGrid.Columns>
|
||||
</c:DataGridEx>
|
||||
</Grid>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
|
||||
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:SyntheticManagementRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -1,12 +1,17 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticManagementView.xaml
|
||||
/// </summary>
|
||||
internal partial class SyntheticManagementView : UserControl {
|
||||
partial class SyntheticManagementView : UserControl {
|
||||
|
||||
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
|
||||
public SyntheticManagementView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,27 +16,33 @@ using System.Windows;
|
||||
using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData> {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
OrdersModel _orders_model;
|
||||
SyntheticManagementView _view;
|
||||
IDisposable _subscriptions;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public static SyntheticManagementWindow create(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state) {
|
||||
var window = new SyntheticManagementWindow(factory, bus, orders_model);
|
||||
window.initialize(state);
|
||||
return window;
|
||||
}
|
||||
|
||||
SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model)
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
Log.notnull(_state);
|
||||
|
||||
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
|
||||
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
|
||||
var current_row_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM.Row>(current_row_source);
|
||||
Commands = new SparkCommand[] {
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
|
||||
() => new SecurityCriteria(), () => true
|
||||
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
|
||||
@@ -51,7 +60,24 @@ namespace mbts.spark {
|
||||
filters = new OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
|
||||
}
|
||||
})
|
||||
}
|
||||
), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
|
||||
_subscriptions = Disposable.Create(
|
||||
@@ -69,21 +95,21 @@ namespace mbts.spark {
|
||||
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
|
||||
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
|
||||
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
|
||||
}
|
||||
|
||||
public void initialize(StartupData state) {
|
||||
Log.notnull(state);
|
||||
_state = state;
|
||||
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, state.sorting_criteria, state.filters ?? new FilterStates());
|
||||
|
||||
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, _settings_manager, _state.sorting_criteria, _state.filters ?? new FilterStates());
|
||||
if (_view == null) _view = new SyntheticManagementView { DataContext = _vm.value };
|
||||
else _view.DataContext = _vm.value;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands { get; }
|
||||
public override StartupData SaveState => _state = new StartupData(_vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
|
||||
public override double MinWidth => 700;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
|
||||
@@ -1,30 +1,46 @@
|
||||
using bts.api;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.SyntheticManagementView;
|
||||
|
||||
namespace bts.shell {
|
||||
class SyntheticManagementWindowFactory : ShellWindowFactory<SyntheticManagementWindowFactory.StartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
|
||||
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "SyntheticManagement") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
Task.FromResult<IShellWindow>(SyntheticManagementWindow.create(this, _bus, _orders_model, state));
|
||||
Task.FromResult<IShellWindow>(_create(state));
|
||||
|
||||
public async void show(SparkTags.Nothing nothing) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.synthetic_management_grid,
|
||||
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
public const int Version = 1;
|
||||
@@ -33,11 +49,41 @@ namespace bts.shell {
|
||||
public readonly List<SortingCriteria> sorting_criteria;
|
||||
[ProtoMember(2)]
|
||||
public readonly FilterStates filters;
|
||||
[ProtoMember(3)]
|
||||
public readonly GridSettings grid;
|
||||
|
||||
public StartupData() : this(new List<SortingCriteria>(), null) { }
|
||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters) {
|
||||
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
|
||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||
this.sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings name => new GridColumnSettings(col_id(Columns.Name), DefaultColumnHeaders.Name, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings description => new GridColumnSettings(col_id(Columns.Description), DefaultColumnHeaders.Description, width: DefaultColumnWidths.Description);
|
||||
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
|
||||
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(name.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return name;
|
||||
yield return description;
|
||||
yield return n_legs;
|
||||
yield return n_working_orders;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using mbts.ui.spark;
|
||||
using LegMarketDataCalculation = bts.spark.LegSpreaderParams.Types.LegMarketDataCalculation;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class MarketDataCalculationModel : RowModelBase<LegMarketDataCalculation> {
|
||||
public override string display_name { get; } = "Market Data Calculation";
|
||||
public override string help_text { get; }
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override LegMarketDataCalculation get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.LegMdCalc;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, LegMarketDataCalculation value) =>
|
||||
leg_tup.leg_params.LegMdCalc = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using mbts.ui.spark;
|
||||
using MarketDataType = bts.spark.LegSpreaderParams.Types.MarketDataType;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class MarketDataTypeModel : RowModelBase<MarketDataType> {
|
||||
public override string display_name { get; } = "Market Data Type";
|
||||
public override string help_text { get; }
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override MarketDataType get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.MarketDataType;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, MarketDataType value) =>
|
||||
leg_tup.leg_params.MarketDataType = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,6 +203,8 @@ namespace mbts.spark {
|
||||
new IsQuoteModel(),
|
||||
new HedgeRatioModel(),
|
||||
new QuoteHiddenModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new MarketDataCalculationModel(),
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using static bts.shell.SyntheticManagementWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.SyntheticManagementView;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model) : this(bus, orders_model, null, null) { }
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
|
||||
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
|
||||
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
@@ -28,7 +37,41 @@ namespace mbts.spark {
|
||||
n_working_orders_filter.load_state(filters.n_working_orders);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
_dispose = DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter);
|
||||
_settings_manager = settings_manager;
|
||||
// columns
|
||||
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
|
||||
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
|
||||
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
new Binding(nameof(Row.show_order_book)),
|
||||
n_working_orders_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
id = col_id(Columns.n_working_orders),
|
||||
Header = DefaultColumnHeaders.N_Working_Orders,
|
||||
Width = DefaultColumnWidths.N_Working_Orders
|
||||
}
|
||||
);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
id = col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width
|
||||
});
|
||||
//
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, 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(
|
||||
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
}
|
||||
|
||||
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
|
||||
@@ -36,6 +79,20 @@ namespace mbts.spark {
|
||||
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
|
||||
@@ -44,7 +101,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public FilterStates get_filter_states() =>
|
||||
FilterStates _get_filter_states() =>
|
||||
new FilterStates {
|
||||
description = description_filter.get_state(),
|
||||
create_time = create_time_filter.get_state(),
|
||||
@@ -55,7 +112,7 @@ namespace mbts.spark {
|
||||
};
|
||||
#endregion
|
||||
|
||||
public class Row : SecurityMDRow, IEquatable<Row> {
|
||||
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
|
||||
readonly IBus _bus;
|
||||
@@ -85,6 +142,17 @@ namespace mbts.spark {
|
||||
public uint n_working_orders => _n_working_orders.Value;
|
||||
public ICommand show_ladder { get; }
|
||||
public ICommand show_order_book { get; }
|
||||
public ICommand default_command => show_ladder;
|
||||
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
|
||||
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
@@ -96,6 +164,8 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
|
||||
|
||||
void _show_ladder() =>
|
||||
_bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
@@ -13,6 +14,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
@@ -23,13 +25,16 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
namespace mbts.spark {
|
||||
class FillsVM : SecurityGrid<FillsVM.Row, FillTuple> {
|
||||
readonly OrdersModel _model;
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, StartupData state)
|
||||
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.CreateIncrementalUpdate(fills_source), null) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account, nameof(Row.account));
|
||||
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users().Select(d => d.Values), r => r.user, nameof(Row.user));
|
||||
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
|
||||
@@ -44,30 +49,6 @@ namespace mbts.spark {
|
||||
SecurityDataHelper.EqualityComparer.IDComparer,
|
||||
Comparer<SecurityImmutable>.Default
|
||||
);
|
||||
if (filters != null) {
|
||||
fill_time_filter.load_state(filters.fill_time);
|
||||
time_in_force_filter.load_state(filters.time_in_force);
|
||||
order_type_filter.load_state(filters.order_type);
|
||||
account_filter.load_state(filters.account);
|
||||
user_filter.load_state(filters.user);
|
||||
exchange_filter.load_state(filters.exchange);
|
||||
security_filter.load_state(filters.security);
|
||||
side_filter.load_state(filters.side);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
fill_kind_filter.load_state(filters.fill_kind);
|
||||
fill_key_filter.load_state(filters.fill_key);
|
||||
order_key_filter.load_state(filters.order_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
} else {
|
||||
fill_time_filter.load_state(
|
||||
new TimeFilterTuple(ui.controls.TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1))
|
||||
)
|
||||
);
|
||||
fill_kind_filter.load_state(EnumHelper.get_values_except(FillKind.Sod).ToArray());
|
||||
}
|
||||
// columns
|
||||
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(), nameof(Row.security), security_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
|
||||
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), nameof(Row.fill_time), fill_time_filter, DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
|
||||
@@ -75,30 +56,83 @@ namespace mbts.spark {
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, nameof(Row.account), account_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), nameof(Row.exchange), exchange_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
|
||||
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), nameof(Row.time_in_force), time_in_force_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
|
||||
columns.Add(new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.order_type)), new Binding(nameof(Row.order_metadata)), order_type_filter) { sort_member_path = nameof(Row.order_type) });
|
||||
columns.Add(new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { sort_member_path = nameof(Row.side) });
|
||||
columns.Add(new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.order_type)), new Binding(nameof(Row.order_metadata)), order_type_filter) { id = col_id(Columns.OrderType), sort_member_path = nameof(Row.order_type) });
|
||||
columns.Add(new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side), sort_member_path = nameof(Row.side) });
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, "Fill Kind", r => r.fill_kind.ToString(), nameof(Row.fill_kind), fill_kind_filter, DataGridFilterKnownTemplates.DiscreteValues, 70);
|
||||
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), nameof(Row.fill_kind), fill_kind_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), nameof(Row.fill_key), fill_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), nameof(Row.order_key), order_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
id = col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width
|
||||
});
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, 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.Add(DataGridFilterHelper.hook_filters(rows_view,
|
||||
fill_time_filter, time_in_force_filter, order_type_filter, account_filter, user_filter, exchange_filter,
|
||||
security_filter, side_filter, price_filter, size_filter, fill_kind_filter, fill_key_filter, order_key_filter, security_key_filter
|
||||
));
|
||||
_resources.AddRange(
|
||||
state_obs.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) {
|
||||
fill_time_filter.load_state(filters.fill_time);
|
||||
time_in_force_filter.load_state(filters.time_in_force);
|
||||
order_type_filter.load_state(filters.order_type);
|
||||
account_filter.load_state(filters.account);
|
||||
user_filter.load_state(filters.user);
|
||||
exchange_filter.load_state(filters.exchange);
|
||||
security_filter.load_state(filters.security);
|
||||
side_filter.load_state(filters.side);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
fill_kind_filter.load_state(filters.fill_kind);
|
||||
fill_key_filter.load_state(filters.fill_key);
|
||||
order_key_filter.load_state(filters.order_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
} else {
|
||||
fill_time_filter.load_state(
|
||||
new TimeFilterTuple(ui.controls.TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1))
|
||||
)
|
||||
);
|
||||
fill_kind_filter.load_state(EnumHelper.get_values_except(FillKind.Sod).ToArray());
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}),
|
||||
DataGridFilterHelper.hook_filters(rows_view,
|
||||
fill_time_filter, time_in_force_filter, order_type_filter, account_filter, user_filter, exchange_filter,
|
||||
security_filter, side_filter, price_filter, size_filter, fill_kind_filter, fill_key_filter, order_key_filter, security_key_filter
|
||||
),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
fills_source.Connect()
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> fill_time_filter { get; } = new TimeFilterVM<Row>(r => r.fill_time, nameof(Row.fill_time));
|
||||
public IDataGridColumnPersistentFilter<Row, TimeInForce[]> time_in_force_filter { get; } = DiscreteValuesFilterVM.Create<Row, TimeInForce>(EnumHelper.get_values<TimeInForce>().ToLArray(), r => r.time_in_force, nameof(Row.time_in_force));
|
||||
@@ -144,6 +178,9 @@ namespace mbts.spark {
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
|
||||
state.SelectManyInSeries(s => _get_observable(bus, model, s));
|
||||
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
|
||||
readonly OrdersModel _model;
|
||||
readonly FillImmutable _fill;
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
|
||||
@@ -13,29 +20,76 @@ namespace mbts.spark {
|
||||
const string _window_type = "Fills", _thread_id = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
FillsVM _vm;
|
||||
FillsView _view;
|
||||
StartupData _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state)
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.rows_view.GetSortingCriteria(), _vm.get_filters());
|
||||
public override double MinWidth => 1050 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.rows_view.get_sorting_criteria(_vm.columns), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, _state);
|
||||
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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())),
|
||||
this.observe_title()
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = $"{_window_type} Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
_subscriptions = Disposable.Create(() =>
|
||||
_state_changed?.Invoke(
|
||||
_state = new StartupData(_state.orders,
|
||||
_vm.rows_view.get_sorting_criteria(_vm.columns),
|
||||
_vm.get_filters(),
|
||||
_vm.grid_settings_manager.create()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public override void Dispose() => Disposable.dispose(ref _vm);
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,118 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
|
||||
namespace bts.shell {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public FillsWindowFactory(TradingShellModule module, IBus bus, OrdersModel orders_model)
|
||||
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
: base(module, "Fills") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
Task.FromResult<IShellWindow>(new FillsWindow(this, _bus, _orders_model, state));
|
||||
Task.FromResult(_create(state));
|
||||
|
||||
public async void show(StartupData state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.fills_grid,
|
||||
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
|
||||
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
public const int Version = 1;
|
||||
public StartupData() { }
|
||||
public StartupData(uint[] orders) : this(orders, null, null) { }
|
||||
public StartupData(FilterStates filters) : this(null, null, filters) { }
|
||||
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters) {
|
||||
this.orders = orders;
|
||||
this.sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
public List<SortingCriteria> sorting_criteria;
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public StartupData() { }
|
||||
public StartupData(uint[] orders) : this(orders, null, null) { }
|
||||
public StartupData(FilterStates filters) : this(null, null, filters) { }
|
||||
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static StartupData Default() =>
|
||||
new StartupData(null, null, new FilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -4,6 +4,7 @@ using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -22,15 +23,17 @@ namespace bts.shell {
|
||||
readonly IWindowManager _wm;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly MarketDataAuxiliaryModel _md_auxiliary_model;
|
||||
readonly int _max_ladders_open;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model, MarketDataAuxiliaryModel md_auxiliary_model)
|
||||
: base(owner, "SparkLadder") {
|
||||
_bus = bus;
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_md_auxiliary_model = md_auxiliary_model;
|
||||
var knobs = Configuration.get<bts.config.LadderKnobs>();
|
||||
_max_ladders_open = (int)(knobs.max_open_ladders);
|
||||
_security_timeout = TimeSpan.FromSeconds(Math.Max(15, knobs.security_timeout_secs));
|
||||
@@ -56,7 +59,7 @@ namespace bts.shell {
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
|
||||
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
var model = new LadderModel(md, _orders_model);
|
||||
var model = new LadderModel(md, _orders_model, _md_auxiliary_model);
|
||||
var settings_manager = new WindowSettingsManager<LadderSettings>(
|
||||
_settings_hub, state_settings,
|
||||
us => sanitize_ladder_settings(us.ladder),
|
||||
@@ -85,6 +88,12 @@ namespace bts.shell {
|
||||
s.session_high_line = LadderSettings.Default.session_high_line;
|
||||
if (s.session_range_fill == null)
|
||||
s.session_range_fill = LadderSettings.Default.session_range_fill;
|
||||
if (s.volume_column == null)
|
||||
s.volume_column = LadderSettings.Default.volume_column;
|
||||
if (s.size_display == null)
|
||||
s.size_display = MarketSizeDisplay.get_default();
|
||||
if (s.size_display.normal == null)
|
||||
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace mbts.spark {
|
||||
where T : struct =>
|
||||
new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T> {
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T>, INotifyPropertyChanged {
|
||||
readonly OrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly string _prop_name;
|
||||
@@ -58,6 +58,10 @@ namespace mbts.spark {
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
OrderBookCell<T> _current;
|
||||
|
||||
// Property changed handler to avoid WPF binding leak
|
||||
// https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
|
||||
public event PropertyChangedEventHandler PropertyChanged = (_,__) => { };
|
||||
|
||||
public OrderBookCellStore(
|
||||
OrderBookRowVM row,
|
||||
ICVOrderBookColumn column,
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
@@ -208,7 +209,7 @@ namespace mbts.spark {
|
||||
"Parent Key",
|
||||
nameof(OrderBookRowVM.parent_order_key),
|
||||
r => r.parent_order_key.store,
|
||||
80,
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderBookRowVM>(
|
||||
r => r.parent_order_key.text,
|
||||
value_path(nameof(OrderBookRowVM.parent_order_key))),
|
||||
@@ -321,6 +322,7 @@ namespace mbts.spark {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
readonly string _filter_template;
|
||||
protected readonly Func<OrderBookRowVM, IOrderBookCellStore> _get_cell_store;
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, IOrderBookCellStore> get_cell_store, string sort_prop, double width, IDataGridColumnPersistentFilter<OrderBookRowVM, T> filter, string filter_template) : base(col_key) {
|
||||
id = key.ToString();
|
||||
@@ -331,6 +333,12 @@ namespace mbts.spark {
|
||||
this.filter = filter;
|
||||
_filter_template = filter_template;
|
||||
_cached_sort_direction = SortDirection;
|
||||
font_params_changed += refresh_glyph_contexts;
|
||||
refresh_glyph_contexts();
|
||||
void refresh_glyph_contexts() {
|
||||
_root_glyph_context = GlyphContext.create(font_family, RenderUtils.root_font_weight);
|
||||
_child_glyph_context = GlyphContext.create(font_family, RenderUtils.child_font_weight);
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderBookRowVM, T> filter { get; }
|
||||
@@ -346,6 +354,7 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = new_value;
|
||||
base.OnSortDirectionChanged(new_value);
|
||||
}
|
||||
protected GlyphContext _get_glyph_context(OrderBookRowVM row) => row.is_child ? _child_glyph_context : _root_glyph_context;
|
||||
}
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
@@ -355,7 +364,7 @@ namespace mbts.spark {
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
var cell = _get_cell_store(row);
|
||||
if (!string.IsNullOrEmpty(cell.current.text))
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, cell.current.text, position, RowHeight,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, cell.current.text, position, RowHeight,
|
||||
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, cell.animations);
|
||||
}
|
||||
}
|
||||
@@ -366,7 +375,7 @@ namespace mbts.spark {
|
||||
: base(Columns.Side, "B/S", r => r.side.store, nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.side.text, position, RowHeight,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.text, position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.side.store.animations);
|
||||
}
|
||||
|
||||
@@ -377,18 +386,18 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.quantity.text, position, RowHeight,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.text, position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.quantity.store.animations);
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.store, nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.store, nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Filled, filter, DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
|
||||
}
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.filled_quantity.text, position, RowHeight,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.text, position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.filled_quantity.store.animations);
|
||||
}
|
||||
|
||||
@@ -397,26 +406,26 @@ namespace mbts.spark {
|
||||
: base(Columns.Price, "Price", r => r.price.store, nameof(OrderBookRowVM.price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.price.text,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.price.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.price.store.animations);
|
||||
}
|
||||
|
||||
public class AvgFilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public AvgFilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
|
||||
: base(Columns.AvgFilled, "Avg Filled", r => r.avg_price.store, nameof(OrderBookRowVM.avg_price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
: base(Columns.AvgFilled, "Avg Filled", r => r.avg_price.store, nameof(OrderBookRowVM.avg_price), DefaultColumnWidths.AveragePrice, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.avg_price.text,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.avg_price.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.avg_price.store.animations);
|
||||
}
|
||||
}
|
||||
|
||||
public class LastFilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public LastFilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
|
||||
: base(Columns.LastPrice, "Last Filled", r => r.last_price.store, nameof(OrderBookRowVM.last_price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
: base(Columns.LastPrice, "Last Filled", r => r.last_price.store, nameof(OrderBookRowVM.last_price), DefaultColumnWidths.LastPrice, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.last_price.text,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.last_price.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.last_price.store.animations);
|
||||
}
|
||||
|
||||
@@ -425,7 +434,7 @@ namespace mbts.spark {
|
||||
: base(Columns.Time, "Time", r => r.time_created.store, nameof(OrderBookRowVM.time_created), DefaultColumnWidths.Time, filter, DataGridFilterKnownTemplates.TimeFilter) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.time_created.text,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.time_created.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.time_created.store.animations);
|
||||
}
|
||||
|
||||
@@ -434,7 +443,7 @@ namespace mbts.spark {
|
||||
: base(Columns.LastChange, "Last Change", r => r.last_change.store, nameof(OrderBookRowVM.last_change), DefaultColumnWidths.Time, filter, DataGridFilterKnownTemplates.TimeFilter) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.last_change.text,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.last_change.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.last_change.store.animations);
|
||||
}
|
||||
|
||||
@@ -470,7 +479,7 @@ namespace mbts.spark {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = 80;
|
||||
Width = DIYGrid.DefaultColumnWidths.LastMessage;
|
||||
sort_member_path = nameof(OrderBookRowVM.last_msg);
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
@@ -521,7 +530,9 @@ namespace mbts.spark {
|
||||
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
|
||||
//
|
||||
var security_view = new TextBlock { FontFamily = font_family, FontSize = font_size, Foreground = font_brush };
|
||||
var security_view = new TextBlock { Foreground = font_brush };
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.TextProperty, $"{nameof(OrderBookRowVM.security)}.{nameof(IGridCell.text)}");
|
||||
security_view.SetBinding(TextBlock.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
@@ -548,7 +559,10 @@ namespace mbts.spark {
|
||||
#endregion
|
||||
|
||||
static class RenderUtils {
|
||||
public static readonly GlyphContext _child_glyph_context = TextUtils.StandardGlyphContext.with(FontWeights.Light);
|
||||
public static FontWeight
|
||||
root_font_weight = FontWeights.Normal,
|
||||
child_font_weight = FontWeights.Light;
|
||||
|
||||
public static double font_size = FontSizes.Small;
|
||||
|
||||
public static readonly IValueConverter
|
||||
@@ -567,7 +581,6 @@ namespace mbts.spark {
|
||||
return mb;
|
||||
}
|
||||
|
||||
public static GlyphContext get_glyph_context(OrderBookRowVM r) => r.is_child ? _child_glyph_context : TextUtils.StandardGlyphContext;
|
||||
public static Brush get_side_brush(OrderBookRowVM r) => r.side.value == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
|
||||
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using OrderStatus = bts.api.orderman.OrderStatus;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM<OrderRecordTuple>, DIYGrid.IActionableRowVM {
|
||||
public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
//public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
@@ -30,31 +30,66 @@ namespace mbts.spark {
|
||||
string _symbol;
|
||||
OrderBookRowVM _parent;
|
||||
OrderBookVM.BranchFilterSource _branch_filter_source;
|
||||
IDisposable _branch_filter_sub;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store;
|
||||
IOrderBookCellStore<uint> _security_key_store;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store;
|
||||
IOrderBookCellStore<Venue> _exchange_store;
|
||||
IOrderBookCellStore<string> _symbol_store;
|
||||
IOrderBookCellStore<uint> _order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _security_key_store_backing;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store_backing;
|
||||
IOrderBookCellStore<Venue> _exchange_store_backing;
|
||||
IOrderBookCellStore<string> _symbol_store_backing;
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store;
|
||||
IOrderBookCellStore<Side> _side_store;
|
||||
IOrderBookCellStore<string> _user_store;
|
||||
IOrderBookCellStore<string> _account_store;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store;
|
||||
IOrderBookCellStore<DateTime> _time_created_store;
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store_backing;
|
||||
IOrderBookCellStore<Side> _side_store_backing;
|
||||
IOrderBookCellStore<string> _user_store_backing;
|
||||
IOrderBookCellStore<string> _account_store_backing;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store_backing;
|
||||
IOrderBookCellStore<DateTime> _time_created_store_backing;
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store;
|
||||
IOrderBookCellStore<OrderStatus> _status_store;
|
||||
IOrderBookCellStore<double> _price_store;
|
||||
IOrderBookCellStore<uint> _quantity_store;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store;
|
||||
IOrderBookCellStore<double> _last_price_store;
|
||||
IOrderBookCellStore<double> _avg_price_store;
|
||||
IOrderBookCellStore<string> _last_msg_store;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store;
|
||||
IOrderBookCellStore<DateTime> _last_change_store_backing;
|
||||
IOrderBookCellStore<OrderStatus> _status_store_backing;
|
||||
IOrderBookCellStore<double> _price_store_backing;
|
||||
IOrderBookCellStore<uint> _quantity_store_backing;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store_backing;
|
||||
IOrderBookCellStore<double> _last_price_store_backing;
|
||||
IOrderBookCellStore<double> _avg_price_store_backing;
|
||||
IOrderBookCellStore<string> _last_msg_store_backing;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store_backing;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store => _order_key_store_backing ?? (_order_key_store_backing = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store));
|
||||
IOrderBookCellStore<uint> _parent_order_key_store => _parent_order_key_store_backing ?? (_parent_order_key_store_backing = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store));
|
||||
IOrderBookCellStore<uint> _security_key_store => _security_key_store_backing ?? (_security_key_store_backing = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store));
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store => _security_store_backing ?? (_security_store_backing = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()));
|
||||
IOrderBookCellStore<Venue> _exchange_store => _exchange_store_backing ?? (_exchange_store_backing = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store));
|
||||
IOrderBookCellStore<string> _symbol_store => _symbol_store_backing ?? (_symbol_store_backing = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"));
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store => _order_type_store_backing ?? (_order_type_store_backing = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store));
|
||||
IOrderBookCellStore<Side> _side_store => _side_store_backing ?? (_side_store_backing = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store));
|
||||
IOrderBookCellStore<string> _user_store => _user_store_backing ?? (_user_store_backing = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store));
|
||||
IOrderBookCellStore<string> _account_store => _account_store_backing ?? (_account_store_backing = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store));
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store => _time_in_force_store_backing ?? (_time_in_force_store_backing = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store));
|
||||
IOrderBookCellStore<DateTime> _time_created_store => _time_created_store_backing ?? (_time_created_store_backing = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date));
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store => _last_change_store_backing ?? (_last_change_store_backing = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date));
|
||||
IOrderBookCellStore<OrderStatus> _status_store => _status_store_backing ?? (_status_store_backing = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store));
|
||||
IOrderBookCellStore<double> _price_store => _price_store_backing ?? (_price_store_backing = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price));
|
||||
IOrderBookCellStore<uint> _quantity_store => _quantity_store_backing ?? (_quantity_store_backing = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store));
|
||||
IOrderBookCellStore<uint> _filled_quantity_store => _filled_quantity_store_backing ?? (_filled_quantity_store_backing = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store));
|
||||
IOrderBookCellStore<double> _last_price_store => _last_price_store_backing ?? (_last_price_store_backing = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price));
|
||||
IOrderBookCellStore<double> _avg_price_store => _avg_price_store_backing ?? (_avg_price_store_backing = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price));
|
||||
IOrderBookCellStore<string> _last_msg_store => _last_msg_store_backing ?? (_last_msg_store_backing = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store));
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store => _order_sub_type_store_backing ?? (_order_sub_type_store_backing = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type));
|
||||
|
||||
static string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_price(double v) => _security.HasValue ?
|
||||
_security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
static string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public OrderBookRowVM(IBus bus, OrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityMD? s) {
|
||||
_book_vm = source_vm;
|
||||
@@ -62,43 +97,7 @@ namespace mbts.spark {
|
||||
_order = record;
|
||||
_security_md = s;
|
||||
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
|
||||
animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
// cells
|
||||
cell_stores = new DIYGrid.IGridCellStore[] {
|
||||
_security_store = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()),
|
||||
_security_key_store = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store),
|
||||
_parent_order_key_store = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store),
|
||||
_order_key_store = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store),
|
||||
_symbol_store = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"),
|
||||
_time_created_store = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date),
|
||||
_user_store = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store),
|
||||
_account_store = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store),
|
||||
_exchange_store = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store),
|
||||
_time_in_force_store = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store),
|
||||
_order_type_store = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store),
|
||||
_order_sub_type_store = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type),
|
||||
_side_store = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store),
|
||||
_last_change_store = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date),
|
||||
_price_store = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price),
|
||||
_quantity_store = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store),
|
||||
_filled_quantity_store = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store),
|
||||
_avg_price_store = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price),
|
||||
_last_price_store = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price),
|
||||
_status_store = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store),
|
||||
_last_msg_store = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store),
|
||||
};
|
||||
//
|
||||
string format_price(double v) => _security.HasValue ? _security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
//
|
||||
//animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
_visible = backing(nameof(is_visible), true);
|
||||
_expanded = backing(nameof(is_expanded), false);
|
||||
_depth_level = backing(nameof(depth_level), () => {
|
||||
@@ -114,15 +113,11 @@ namespace mbts.spark {
|
||||
_show_order_modify_cmd = new ObservantCommand(() => bus.post(SparkTags.ShowModifyOrderTag, _order.Key));
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
_passes_filter = backing(nameof(passes_filter),
|
||||
Observable.FromEvent<Action<IObservable<bool>>, IObservable<bool>>(
|
||||
cb => _branch_filter_source_changed += cb, cb => _branch_filter_source_changed -= cb)
|
||||
.SelectMany(o => o));
|
||||
_passes_filter = backing(nameof(passes_filter), row_passes_filter);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderRecordTuple old_val, OrderRecordTuple new_val)> updated;
|
||||
event Action<IObservable<bool>> _branch_filter_source_changed;
|
||||
|
||||
public OrderRecordImmutable order => _order;
|
||||
public OrderMetadata order_metadata => _model.factory.get_metadata(order_type.value);
|
||||
@@ -217,7 +212,31 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores { get; }
|
||||
|
||||
DIYGrid.IGridCellStore[] _cell_stores_backing;
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => _cell_stores_backing ?? (_cell_stores_backing = new DIYGrid.IGridCellStore[] {
|
||||
_security_store,
|
||||
_security_key_store,
|
||||
_parent_order_key_store,
|
||||
_order_key_store,
|
||||
_symbol_store,
|
||||
_time_created_store,
|
||||
_user_store,
|
||||
_account_store,
|
||||
_exchange_store,
|
||||
_time_in_force_store,
|
||||
_order_type_store,
|
||||
_order_sub_type_store,
|
||||
_side_store,
|
||||
_last_change_store,
|
||||
_price_store,
|
||||
_quantity_store,
|
||||
_filled_quantity_store,
|
||||
_avg_price_store,
|
||||
_last_price_store,
|
||||
_status_store,
|
||||
_last_msg_store
|
||||
});
|
||||
|
||||
public static Stack<OrderBookRowVM> lineage(OrderBookRowVM node) {
|
||||
var rv = new Stack<OrderBookRowVM>();
|
||||
@@ -228,11 +247,15 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void join_branch_filter_source(OrderBookVM.BranchFilterSource new_branch_filter_source) {
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.passes_filter_changed -= _passes_filter.set;
|
||||
}
|
||||
_branch_filter_source = new_branch_filter_source;
|
||||
_branch_filter_source.set_row_passes_filter(_order.Key, row_passes_filter);
|
||||
_branch_filter_source_changed?.Invoke(_branch_filter_source);
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.join_branch_filter_source(_branch_filter_source);
|
||||
_passes_filter.set(_branch_filter_source.passes_filter);
|
||||
_branch_filter_source.passes_filter_changed += _passes_filter.set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,7 +334,13 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() => (int)_order.Key;
|
||||
public override string ToString() => $"{order_key} {last_change} {parent_order_key}";
|
||||
|
||||
protected override void Dispose(bool disposing) => Disposable.dispose(ref _branch_filter_sub);
|
||||
protected override void Dispose(bool disposing) {
|
||||
// idempotent
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.passes_filter_changed -= _passes_filter.set;
|
||||
_branch_filter_source = null;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<OrderBookRowVM> _descendents(OrderBookRowVM root) {
|
||||
foreach (var child in root.child_orders) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
@@ -22,6 +24,7 @@ using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using _GridSettingsManager = mbts.ui.GridSettingsManager<mbts.spark.OrderBookRowVM, mbts.spark.OrderRecordTuple, bts.shell.INotificationServiceDataCache, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
@@ -29,6 +32,7 @@ namespace mbts.spark {
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> _order_securities_subject =
|
||||
new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
OrderBookStartupData _state;
|
||||
|
||||
Dictionary<uint, OrderBookRowVM> _order_rows_cache = new Dictionary<uint, OrderBookRowVM>();
|
||||
@@ -38,8 +42,10 @@ namespace mbts.spark {
|
||||
SetMap<uint, uint> _parent_to_children = new SetMap<uint, uint>();
|
||||
SetMap<uint, uint> _security_orders = new SetMap<uint, uint>();
|
||||
|
||||
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model) {
|
||||
columns = new OrderBookColumnCollection(bus, model, _order_securities_subject);
|
||||
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager) {
|
||||
_settings_manager = settings_manager;
|
||||
var order_securities_subject = new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
columns = new OrderBookColumnCollection(bus, model, order_securities_subject);
|
||||
rows_view = CollectionViewSource.GetDefaultView(_order_rows);
|
||||
rows_view.Filter = obj => obj is OrderBookRowVM vm && vm.is_visible && vm.passes_filter;
|
||||
columns.filter_changed += () => {
|
||||
@@ -64,6 +70,7 @@ namespace mbts.spark {
|
||||
});
|
||||
|
||||
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, _order_rows, rows_view, columns);
|
||||
grid_settings_manager.load_from(OrderBookStartupData.DefaultGrid);
|
||||
_live_sorting_columns = new LiveSortingColumKeys(grid_settings_manager.live_sorting_props);
|
||||
|
||||
SetMap<uint, uint> missing_security_orders = new SetMap<uint, uint>();
|
||||
@@ -71,11 +78,13 @@ namespace mbts.spark {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.OnDispatcher().AddRemoveOnly().Subscribe(on_orders),
|
||||
model.on_orders.OnDispatcher().Subscribe(on_orders),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
grid_settings_manager,
|
||||
columns);
|
||||
columns,
|
||||
order_securities_subject);
|
||||
|
||||
void on_orders(LegacyTableUpdate<OrderRecordImmutable, uint> table) {
|
||||
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
|
||||
// clear everything (most likely the server restarted)
|
||||
if (table.removed.Count() == _order_rows.Count && !table.added.Any()) {
|
||||
_selected_rows.Clear();
|
||||
@@ -86,18 +95,14 @@ namespace mbts.spark {
|
||||
_parent_to_children.clear();
|
||||
_security_orders.clear();
|
||||
missing_security_orders.clear();
|
||||
Log.Ui.warn("OrderBook received reset message.");
|
||||
return;
|
||||
}
|
||||
var changed_keys = new HashSet<uint>();
|
||||
var added_securities = new List<SecurityImmutable>(1);
|
||||
var removed_securities = new List<SecurityImmutable>(1);
|
||||
|
||||
foreach (var o in table.removed) {
|
||||
// if state has it, then it wasn't actually removed, just changed
|
||||
if (table.state.TryGetValue(o.Key, out OrderRecordImmutable changed_order)) {
|
||||
changed_keys.Add(o.Key);
|
||||
_order_rows_cache[o.Key].update_order(changed_order);
|
||||
} else if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
row.Dispose();
|
||||
_order_rows.Remove(row);
|
||||
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
|
||||
@@ -124,10 +129,10 @@ namespace mbts.spark {
|
||||
missing_security_orders.remove(o.Key);
|
||||
}
|
||||
}
|
||||
foreach (var ch in table.changes)
|
||||
_order_rows_cache[ch.New.Key].update_order(ch.New);
|
||||
var orphans = new Dictionary<uint, OrderBookRowVM>();
|
||||
foreach (var o in table.added) {
|
||||
if (changed_keys.Contains(o.Key))
|
||||
continue;
|
||||
SecurityMD? security_md;
|
||||
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
|
||||
security_md = smd;
|
||||
@@ -170,7 +175,7 @@ namespace mbts.spark {
|
||||
_order_rows.Add(row);
|
||||
}
|
||||
if (added_securities.Count > 0 || removed_securities.Count > 0)
|
||||
_order_securities_subject.OnNext((added_securities, removed_securities));
|
||||
order_securities_subject.OnNext((added_securities, removed_securities));
|
||||
|
||||
void try_add_children(OrderBookRowVM row) {
|
||||
if (_parent_to_children.try_lookup(row.order.Key, out ISet<uint> children))
|
||||
@@ -206,7 +211,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
if (added_securities.Count > 0)
|
||||
_order_securities_subject.OnNext((added_securities, new List<SecurityImmutable>(0)));
|
||||
order_securities_subject.OnNext((added_securities, new List<SecurityImmutable>(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +224,7 @@ namespace mbts.spark {
|
||||
public OrderRecordImmutable? current_order => current_row?.order;
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
|
||||
|
||||
public GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate> grid_settings_manager { get; }
|
||||
public _GridSettingsManager grid_settings_manager { get; }
|
||||
public ICommand collapse_all_rows_command { get; }
|
||||
public ICommand expand_all_rows_command { get; }
|
||||
|
||||
@@ -229,21 +234,27 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public OrderBookStartupData create() {
|
||||
var (grid, animations) = grid_settings_manager.create();
|
||||
return new OrderBookStartupData(columns.create(), grid, animations.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications)));
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return new OrderBookStartupData(columns.create(), use_defaults: true);
|
||||
var gs = grid_settings_manager.create();
|
||||
return new OrderBookStartupData(columns.create(), gs.general, gs.predicate_animations, use_defaults: false);
|
||||
}
|
||||
|
||||
public void load_from(OrderBookStartupData state) {
|
||||
if (state == null) return;
|
||||
_state = state;
|
||||
grid_settings_manager.load_from((state.grid, state.predicate_animations));
|
||||
var gs = state.grid_settings_wrapper();
|
||||
if (gs != null) grid_settings_manager.load_from(gs);
|
||||
columns.load_from(state.filters);
|
||||
}
|
||||
|
||||
internal void remove_branch_filter_root(uint key) =>
|
||||
_branch_filter_roots.Remove(key);
|
||||
|
||||
static GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate> _create_grid_settings_manager(
|
||||
protected override void Dispose(bool disposing) =>
|
||||
Disposable.dispose(_order_rows);
|
||||
|
||||
static _GridSettingsManager _create_grid_settings_manager(
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model,
|
||||
ObservableCollection<OrderBookRowVM> rows, ICollectionView rows_view, ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
@@ -255,7 +266,7 @@ namespace mbts.spark {
|
||||
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
|
||||
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
}
|
||||
return new GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate>(
|
||||
return new _GridSettingsManager(
|
||||
owner_title, bus, notification_service, model, rows, rows_view, columns,
|
||||
() => {
|
||||
var pvm = PredicateBuilderVMWrapper.create<OrderRecordTuple, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
@@ -268,7 +279,9 @@ namespace mbts.spark {
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
|
||||
return pvm;
|
||||
}, notification_service.data_cache, col_settings_flags);
|
||||
},
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications))),
|
||||
notification_service.data_cache, col_settings_flags);
|
||||
}
|
||||
|
||||
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
|
||||
@@ -297,24 +310,21 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
internal class BranchFilterSource : IObservable<bool> {
|
||||
internal class BranchFilterSource {
|
||||
readonly HashSet<uint> _orders_pass_filter = new HashSet<uint>();
|
||||
event Action<bool> _passes_filter_changed;
|
||||
|
||||
public event Action<bool> passes_filter_changed;
|
||||
|
||||
public bool passes_filter => _orders_pass_filter.Count > 0;
|
||||
|
||||
public void set_row_passes_filter(uint key, bool value) {
|
||||
if (value) {
|
||||
if (_orders_pass_filter.Add(key) && _orders_pass_filter.Count == 1)
|
||||
_passes_filter_changed?.Invoke(value);
|
||||
passes_filter_changed?.Invoke(value);
|
||||
} else if (_orders_pass_filter.Remove(key) && _orders_pass_filter.Count == 0) {
|
||||
_passes_filter_changed?.Invoke(value);
|
||||
passes_filter_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<bool> observer) {
|
||||
_passes_filter_changed += observer.OnNext;
|
||||
observer.OnNext(_orders_pass_filter.Count > 0);
|
||||
return Disposable.Create(() => _passes_filter_changed -= observer.OnNext);
|
||||
}
|
||||
}
|
||||
|
||||
class LiveSortingColumKeys : HashSet<Columns> {
|
||||
|
||||
@@ -15,11 +15,9 @@ namespace mbts.spark {
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public const double RowHeight = 17;
|
||||
OrderBookVM _vm;
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
_vm = vm;
|
||||
DataContext = vm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
//using IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
|
||||
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
|
||||
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
|
||||
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook";
|
||||
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook", _default_title = "Order Book";
|
||||
readonly INotificationService _notification_service;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
OrderBookWindowFactory.OrderBookStartupData _state;
|
||||
OrdersModel _model;
|
||||
OrderBookVM _vm;
|
||||
@@ -32,12 +41,14 @@ namespace mbts.spark {
|
||||
, _toggle_expand_all_command
|
||||
, _show_grid_settings_command;
|
||||
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service, OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon)
|
||||
: base("Order Book", bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
||||
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
|
||||
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
_model = model;
|
||||
_factory = factory;
|
||||
_notification_service = notification_service;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
}
|
||||
|
||||
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm.create();
|
||||
@@ -45,8 +56,11 @@ namespace mbts.spark {
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override double MinWidth => 1577;
|
||||
|
||||
public override double MinWidth =>
|
||||
OrderBookWindowFactory.OrderBookStartupData.DefaultGrid.general.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id => ORDERBOOK_THREAD_ID;
|
||||
|
||||
public override void Dispose() {
|
||||
@@ -55,7 +69,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model);
|
||||
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model, _settings_manager);
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
@@ -106,9 +120,32 @@ namespace mbts.spark {
|
||||
Icons.DownArrow,
|
||||
_vm.expand_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(_vm.grid_settings_manager.launch_settings_editor);
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => {
|
||||
if (s.general is null)
|
||||
s = OrderBookWindowFactory.OrderBookStartupData.DefaultGrid;
|
||||
return _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.general.with(_vm.columns.Select(c => c.create())),
|
||||
this.observe_title()
|
||||
);
|
||||
}
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { DataContext = vm };
|
||||
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
|
||||
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
|
||||
});
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
_commands = new SparkCommand[] {
|
||||
_show_fills_command,
|
||||
_modify_order_command,
|
||||
_cancel_selected_orders_command,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using Google.Protobuf.Collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
@@ -11,6 +12,7 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -22,19 +24,22 @@ namespace bts.shell {
|
||||
readonly INotificationService _notification_service;
|
||||
readonly INotificationServiceDataCache _notification_service_data_cache;
|
||||
readonly OrdersModel _model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model) : base(owner, "SparkOrderBookWindow") {
|
||||
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model, UserSettingsHub settings_hub) : base(owner, "SparkOrderBookWindow") {
|
||||
_bus = bus;
|
||||
_notification_service = notification_service;
|
||||
_notification_service_data_cache = notification_service_data_cache;
|
||||
_model = model;
|
||||
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(OrderBookStartupData state) {
|
||||
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon) as IShellWindow;
|
||||
return Task.FromResult(w);
|
||||
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
|
||||
us => us.order_book_grid, (us, s) => us.with(order_book_grid: s));
|
||||
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
|
||||
return Task.FromResult<IShellWindow>(w);
|
||||
}
|
||||
|
||||
async void show(OrderBookStartupData startup_data) {
|
||||
@@ -49,27 +54,42 @@ namespace bts.shell {
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid;
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
public OrderBookStartupData() { }
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
this.grid = grid;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() => use_defaults ? null : new OrderBookGridSettings(grid, predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates {
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
}, GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion));
|
||||
}, _default_general_grid, use_defaults: true);
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
|
||||
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
@@ -85,14 +105,14 @@ namespace bts.shell {
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: 80);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, width: DIYGrid.DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -171,5 +191,38 @@ namespace bts.shell {
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class OrderBookGridSettings : IGridSettingsWithNotifications<OrderRecordTuple, OrderRecordTuplePredicate>, IEquatable<OrderBookGridSettings> {
|
||||
[ProtoMember(1)]
|
||||
public GridSettings general { get; set; }
|
||||
[ProtoMember(2)]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
|
||||
public OrderBookGridSettings() { }
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
|
||||
this.general = general;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
}
|
||||
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
IEnumerable<IPredicateAnimationsData<OrderRecordTuple, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderRecordTuple, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
|
||||
|
||||
public OrderBookGridSettings with(GridSettings general) =>
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations);
|
||||
|
||||
public bool Equals(OrderBookGridSettings other) =>
|
||||
other != null &&
|
||||
EqualityComparer.null_check_equals(general, other.general) &&
|
||||
EqualityComparer.safe_sequence_equals(predicate_animations, other.predicate_animations);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as OrderBookGridSettings);
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
EqualityComparer.safe_get_hash_code(general),
|
||||
EqualityComparer.safe_aggregate_hash(predicate_animations));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,38 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.PAndLView;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace bts.shell {
|
||||
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly PAndLModel _model;
|
||||
public PAndLWindowFactory(IShellModule owner, IBus bus, PAndLModel model) : base(owner, "SparkPAndL") {
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, PAndLModel model) : base(owner, "SparkPAndL") {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(PAndLStartupData state) {
|
||||
var w = new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon) as IShellWindow;
|
||||
return Task.FromResult(w);
|
||||
return Task.FromResult(_create(state));
|
||||
}
|
||||
|
||||
async void show(PAndLStartupData startup_data) {
|
||||
@@ -32,6 +40,14 @@ namespace bts.shell {
|
||||
_bus.post(SparkTags.ShowWindow, w);
|
||||
}
|
||||
|
||||
IShellWindow _create(PAndLStartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.pnl_grid,
|
||||
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
return new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class PAndLStartupData {
|
||||
|
||||
@@ -40,21 +56,75 @@ namespace bts.shell {
|
||||
/// </summary>
|
||||
public PAndLStartupData() { }
|
||||
|
||||
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type) {
|
||||
this.sort_criteria = sorting_criteria;
|
||||
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type, GridSettings grid = default) {
|
||||
_sort_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.calc_type = calc_type;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sort_criteria ?? (_sort_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static PAndLStartupData Default() =>
|
||||
new PAndLStartupData(null, new FilterStates(), pnl_calc_type.midpoint);
|
||||
|
||||
|
||||
[ProtoMember(5)]
|
||||
public readonly List<SortingCriteria> sort_criteria;
|
||||
List<SortingCriteria> _sort_criteria;
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(15)]
|
||||
public pnl_calc_type calc_type;
|
||||
//[ProtoMember(20)]
|
||||
// ProtoMember 20 cannot be used further
|
||||
[ProtoMember(25)]
|
||||
public GridSettings grid;
|
||||
|
||||
static class DefaultColumns {
|
||||
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
|
||||
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_pnl => new GridColumnSettings(col_id(Columns.DayLong), DefaultColumnHeaders.DayLong, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_pnl => new GridColumnSettings(col_id(Columns.DayShort), DefaultColumnHeaders.DayShort, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_pnl => new GridColumnSettings(col_id(Columns.DayPNL), DefaultColumnHeaders.DayPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(account.id, true);
|
||||
yield return new SortingCriteria(exchange.id, true);
|
||||
yield return new SortingCriteria(symbol.id, true);
|
||||
yield return new SortingCriteria(security.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return symbol;
|
||||
yield return security;
|
||||
yield return security_type;
|
||||
yield return start_net_position;
|
||||
yield return start_pnl;
|
||||
yield return day_long_pnl;
|
||||
yield return day_short_pnl;
|
||||
yield return day_pnl;
|
||||
yield return total_net_pos;
|
||||
yield return total_pnl;
|
||||
yield return pnl_price;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
@@ -19,11 +20,12 @@ namespace bts.shell {
|
||||
PAndLWindowFactory _plwf;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model) {
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model);
|
||||
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model);
|
||||
_fwf = new FillsWindowFactory(this, bus, orders_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, pandl_model);
|
||||
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache,
|
||||
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, MarketDataAuxiliaryModel md_auxiliary_model) {
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, md_auxiliary_model);
|
||||
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
|
||||
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Trades;
|
||||
|
||||
@@ -53,18 +53,22 @@ namespace mbts.ui {
|
||||
ToastNotificationController _toast_controller;
|
||||
SoundNotificationPlayer _sound_player;
|
||||
Dispatcher _dispatcher;
|
||||
bool _toast_notifications_enabled, _sound_notifications_enabled = NotificationSettings.Default.sound_notification.enabled;
|
||||
bool
|
||||
_sound_notifications_enabled = Configuration.get<bts.config.toggles.SoundNotifications>().enabled,
|
||||
_toast_notifications_enabled = Configuration.get<bts.config.toggles.ToastNotifications>().enabled;
|
||||
|
||||
[ImportingConstructor]
|
||||
public Notifier(IBus bus, UserSettingsHub settings_hub) {
|
||||
_disposer = new ThreadSafeDisposable(dispose_core);
|
||||
Thread thread = new Thread(start) {
|
||||
IsBackground = true,
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
Name = "BTSNotifications"
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
if (_sound_notifications_enabled || _toast_notifications_enabled) {
|
||||
Thread thread = new Thread(start) {
|
||||
IsBackground = true,
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
Name = "BTSNotifications"
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
void start() {
|
||||
try {
|
||||
@@ -102,7 +106,9 @@ namespace mbts.ui {
|
||||
(_dispatcher = Dispatcher.CurrentDispatcher).UnhandledException += _on_dispatcher_UnhandledException;
|
||||
Dispatcher.Run();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (NAudio.MmException) { }
|
||||
catch (Exception ex) {
|
||||
Log.assert(false, $"Exception in {Thread.CurrentThread.Name} thread: {ex}");
|
||||
_disposer.Dispose();
|
||||
}
|
||||
|
||||
@@ -20,23 +20,23 @@ namespace mbts.spark
|
||||
public static void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory)
|
||||
{
|
||||
// var dyntypes = _list.Value;
|
||||
// foreach (var d in dyntypes.dynamic_orders)
|
||||
// {
|
||||
// var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
// dorder.Dynamic.DynamicOrderType = d.type_name;
|
||||
// foreach (var p in d.parameter_keys)
|
||||
// dorder.Dynamic.Parameters.Add(p, string.Empty);
|
||||
// defs.Add(new CustomOrderDefinitionImmutable(dorder));
|
||||
// }
|
||||
var dyntypes = _list.Value;
|
||||
foreach (var d in dyntypes.dynamic_orders)
|
||||
{
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
dorder.Dynamic.DynamicOrderType = d.type_name;
|
||||
foreach (var p in d.parameter_keys)
|
||||
dorder.Dynamic.Parameters.Add(p, string.Empty);
|
||||
defs.Add(new CustomOrderDefinitionImmutable(dorder));
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<NameValueVM> get_parameters(string dyntype)
|
||||
{
|
||||
List<NameValueVM> par = new List<NameValueVM>();
|
||||
// var order = _list.Value.dynamic_orders.FirstOrDefault(x => x.type_name == dyntype);
|
||||
// if (order != null)
|
||||
// par.AddRange(order.parameter_keys.Select(x => new NameValueVM() { name = x, value = string.Empty }));
|
||||
var order = _list.Value.dynamic_orders.FirstOrDefault(x => x.type_name == dyntype);
|
||||
if (order != null)
|
||||
par.AddRange(order.parameter_keys.Select(x => new NameValueVM() { name = x, value = string.Empty }));
|
||||
return par;
|
||||
}
|
||||
}
|
||||
@@ -115,17 +115,17 @@ namespace mbts.spark
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
// if (_child_order == null)
|
||||
// _select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
// _validate();
|
||||
// if (!HasErrors)
|
||||
// {
|
||||
// var i = factory.dynamic_order();
|
||||
// i.DynamicOrderType = _dyn_type;
|
||||
// set_parameter_dictionary(i.Parameters);
|
||||
// child_order.assign_to(i.Child);
|
||||
// value.Dynamic = i;
|
||||
// }
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.dynamic_order();
|
||||
i.DynamicOrderType = _dyn_type;
|
||||
set_parameter_dictionary(i.Parameters);
|
||||
child_order.assign_to(i.Child);
|
||||
value.Dynamic = i;
|
||||
}
|
||||
}
|
||||
|
||||
void set_parameter_dictionary(Google.Protobuf.Collections.MapField<string, string> par)
|
||||
@@ -208,4 +208,4 @@ namespace mbts.spark
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,15 @@ namespace mbts.spark
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(10, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(2, false);
|
||||
trade_toward_behavior = new InlineUserProperty<LeanOrderDefinition.Types.OnToward>(LeanOrderDefinition.Types.OnToward.Cancel, false);
|
||||
}
|
||||
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
|
||||
public readonly InlineUserProperty<uint> max_chase_ticks;
|
||||
public readonly InlineUserProperty<LeanOrderDefinition.Types.OnToward> trade_toward_behavior;
|
||||
}
|
||||
|
||||
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
@@ -44,7 +46,8 @@ namespace mbts.spark
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
public uint max_chase_ticks { get; set; } = 2;
|
||||
public LeanOrderDefinition.Types.OnToward trade_toward_behavior { get; set; } = LeanOrderDefinition.Types.OnToward.Cancel;
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
@@ -77,6 +80,11 @@ namespace mbts.spark
|
||||
max_chase_ticks = p.max_chase_ticks.value;
|
||||
RaisePropertyChanged(nameof(max_chase_ticks));
|
||||
}
|
||||
if (p.trade_toward_behavior.force_override)
|
||||
{
|
||||
trade_toward_behavior = p.trade_toward_behavior.value;
|
||||
RaisePropertyChanged(nameof(trade_toward_behavior));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
@@ -94,10 +102,13 @@ namespace mbts.spark
|
||||
var i = factory.lean_order();
|
||||
i.CrossThreshold = cross_threshold;
|
||||
i.MaxChaseTicks = max_chase_ticks;
|
||||
i.TradeTowardBehavior = trade_toward_behavior;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Lean = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LeanOrderDefinition.Types.OnToward> trade_toward_types => EnumHelper.get_values_except<LeanOrderDefinition.Types.OnToward>();
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace mbts.spark {
|
||||
_security_md.Value.md.get_bbo(ref _bbo_ref, out co2.NativePriceSize bid, out co2.NativePriceSize ask);
|
||||
_best_bid = bid.price;
|
||||
_best_ask = ask.price;
|
||||
_security_md.Value.md.get_last_trade(ref _last_trade_ref, out co2.NativePriceSize ps, out ulong v);
|
||||
_security_md.Value.md.get_last_trade(ref _last_trade_ref, out co2.NativePriceSize ps, out DateTime ts, out ulong v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace mbts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error) {
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error, AdminModel admin_model) {
|
||||
_bus = bus;
|
||||
_factory = factory;
|
||||
_service = service;
|
||||
@@ -41,15 +41,15 @@ namespace mbts.spark {
|
||||
_error = error;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).Table().Cache();
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
working_orders =
|
||||
on_orders.Scan(
|
||||
ImmutableDictionary<uint,OrderRecordImmutable>.Empty,
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
on_order_tuples = OrderRecordTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders);
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).Table().Cache();
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
|
||||
@@ -75,6 +75,11 @@
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
|
||||
</Target>
|
||||
<Target Name="copy_defaults" AfterTargets="PostBuildEvent">
|
||||
|
||||
@@ -521,12 +521,14 @@
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ItemsControl}">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
|
||||
>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
|
||||
>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace bts.spark
|
||||
public static SparkCommand ShowWorkspaces =>
|
||||
SparkCommand.create("Workspaces", Icons.FavoriteWindow, SparkTags.ShowWorkspaces);
|
||||
public static SparkCommand ShowSettings =>
|
||||
SparkCommand.create("Settings", Icons.AdminFrozen, SparkTags.ShowSettings);
|
||||
SparkCommand.create("Settings", Icons.Admin, SparkTags.ShowSettings);
|
||||
|
||||
// Support
|
||||
public static SparkCommand ShowManual =>
|
||||
@@ -113,12 +113,12 @@ namespace bts.spark
|
||||
|
||||
// TODO: remove this method, eventually want all windows to use window_settings_command()
|
||||
public static SparkCommand change_securities_command(Func<(PartialCommand<SecurityCriteria>, SecurityCriteria)> get_param) =>
|
||||
SparkCommand.create("Settings", Icons.AdminFrozen, SparkTags.ShowAndEditPickMultiple, get_param);
|
||||
SparkCommand.create("Securities", Icons.HandPointingOverDocument, SparkTags.ShowAndEditPickMultiple, get_param);
|
||||
|
||||
public static PartialCommand<SecurityCriteria> change_securities_callback(string window_name) =>
|
||||
SparkCommand.create(window_name, Icons.AdminFrozen, new SparkTags.SparkTag<SecurityCriteria>($"Change {window_name} securities callback"));
|
||||
SparkCommand.create(window_name, Icons.HandPointingOverDocument, new SparkTags.SparkTag<SecurityCriteria>($"Change {window_name} securities callback"));
|
||||
|
||||
public static SparkCommand window_settings_command(IDialogCommand launch_settings_editor) =>
|
||||
new SparkCommandWrapper("Settings", Icons.AdminFrozen, launch_settings_editor);
|
||||
new SparkCommandWrapper("Settings", Icons.Admin, launch_settings_editor);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using mbts.collections.extensions;
|
||||
using System.Collections;
|
||||
using bts.collections;
|
||||
|
||||
namespace mbts.ui.utility
|
||||
{
|
||||
|
||||
@@ -5,6 +5,9 @@ namespace mbts.utils {
|
||||
public static class Argument {
|
||||
public static ArgumentException must_be(string description, string name) =>
|
||||
new ArgumentException($"Must be {description}.", name);
|
||||
|
||||
public static ArgumentException unexpected_case<T>(T value, string name) =>
|
||||
new ArgumentException($"Unexpected {typeof(T)} '{value}.'", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,6 +608,12 @@ namespace mbts.ui {
|
||||
public static BTSColor positive_diff { get { return Color.FromRgb(0, 90, 50); } }
|
||||
}
|
||||
|
||||
public static class LadderSpecial {
|
||||
public static BTSColor passive_volume_bar { get; } = Colors.SlateBlue;
|
||||
public static BTSColor total_volume_bar { get; } = 0x009B69;
|
||||
public static BTSColor default_column_background { get; } = 0xBABABA;
|
||||
}
|
||||
|
||||
// Style Guide Colors
|
||||
public static Color brand_lightest { get { return UI.brand_lightest; } }
|
||||
public static Brush brand_lightest_brush { get { return new SolidColorBrush(brand_lightest); } }
|
||||
|
||||
@@ -271,9 +271,6 @@ namespace System.Collections.Generic {
|
||||
#endregion
|
||||
|
||||
#region Max
|
||||
public static T Max<TElement, T>(this IEnumerable<TElement> enumerable, Func<TElement, T> select_value) where T : IComparable<T> =>
|
||||
Max(enumerable, select_value, default(T));
|
||||
|
||||
public static T Max<TElement, T>(this IEnumerable<TElement> enumerable, Func<TElement, T> select_value, T return_if_empty) where T : IComparable<T> {
|
||||
var etr = enumerable.GetEnumerator();
|
||||
if (!etr.MoveNext()) return return_if_empty;
|
||||
@@ -666,44 +663,6 @@ namespace mbts.collections.extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
#region RemoveWhere
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
|
||||
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
|
||||
int count = list.Count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
var t = list[i];
|
||||
if (where(t)) {
|
||||
list.RemoveAt(i--);
|
||||
--count;
|
||||
on_remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rng = new Random();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list) {
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
#region GetValueOrDefault
|
||||
|
||||
@@ -153,40 +153,6 @@ namespace mbts
|
||||
return rv.ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
}
|
||||
|
||||
//this overloaded declaration, (as opposed to one function with comp = null) allows observable_collection.sync_with to be passed into functions as a parameterless Action
|
||||
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src) { dest.sync_with(src, EqualityComparer<T>.Default); }
|
||||
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
Log.notnull(dest);
|
||||
if (src == null) {
|
||||
dest.Clear();
|
||||
return;
|
||||
}
|
||||
var srcList = src as IList<T> ?? src.ToList();
|
||||
var changes = bts.utils.Diff.diff(dest, srcList, comp).ToList();
|
||||
// Apply the diff
|
||||
for(int i = 0; i < changes.Count; ++i) {
|
||||
var c = changes[i];
|
||||
// removes
|
||||
for (var x = 0; x < c.deletedLeft; ++x) {
|
||||
dest.RemoveAt(c.StartLeft);
|
||||
}
|
||||
// inserts
|
||||
for (var x = 0; x < c.insertedRight; ++x) {
|
||||
dest.Insert(c.StartLeft + x, srcList[c.StartRight + x]);
|
||||
}
|
||||
|
||||
// Adjust start index of remaining changes
|
||||
for (int ii = i + 1; ii < changes.Count; ++ii) {
|
||||
var cc = changes[ii];
|
||||
var delta = c.insertedRight - c.deletedLeft;
|
||||
if (cc.StartLeft >= c.StartLeft) {
|
||||
cc.StartLeft += delta;
|
||||
changes[ii] = cc;//remember: struct
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool sync_with_setwise<T>(this ObservableCollection<T> dest, IEnumerable<T> src) { return dest.sync_with_setwise(src, EqualityComparer<T>.Default); }
|
||||
public static bool sync_with_setwise<T>(this ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
if (!src.is_distinct(comp)) {
|
||||
|
||||
@@ -103,9 +103,23 @@ namespace mbts {
|
||||
return _round_tick(tuf, val);
|
||||
}
|
||||
|
||||
public static (int n_low, int n_high) tickdiff(this RegionalTickFactors regional_tick, double x, double y) {
|
||||
if (DoubleHelper.IsNaNOrInfinity(x)) throw new ArgumentException("Cannot be NaN or Infinity.", nameof(x));
|
||||
if (DoubleHelper.IsNaNOrInfinity(y)) throw new ArgumentException("Cannot be NaN or Infinity.", nameof(y));
|
||||
public static bool try_get_tickdiff(this RegionalTickFactors regional_tick, double x, double y, out int n_low, out int n_high) {
|
||||
if (DoubleHelper.IsNaNOrInfinity(x) || DoubleHelper.IsNaNOrInfinity(y)) {
|
||||
n_low = default;
|
||||
n_high = default;
|
||||
return false;
|
||||
}
|
||||
(n_low, n_high) = tickdiff_unchecked(regional_tick, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callers should be certain that <paramref name="x"/> and <paramref name="y"/> are not nan or infinity.
|
||||
/// </summary>
|
||||
/// <param name="regional_tick"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
internal static (int n_low, int n_high) tickdiff_unchecked(this RegionalTickFactors regional_tick, double x, double y) {
|
||||
double ltie = regional_tick.low_tick_inclusive_end;
|
||||
double raw_diff = x - y;
|
||||
if (x < ltie) {
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace mbts.ui.settings
|
||||
}
|
||||
|
||||
public Task<IShellWindow> restore(WindowState window) {
|
||||
Log.Ui.info("Loading window '{0}'".fmt(window.title));
|
||||
bool changed;
|
||||
int count = 0;
|
||||
do {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using bts.proto;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
@@ -12,7 +12,12 @@ namespace mbts.spark {
|
||||
public static UserSettings factory_defaults =>
|
||||
new UserSettings {
|
||||
_ladder = LadderSettings.Default,
|
||||
_notification = NotificationSettings.Default
|
||||
_notification = NotificationSettings.Default,
|
||||
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
|
||||
_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings,
|
||||
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
|
||||
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
|
||||
_order_book_grid = bts.shell.OrderBookWindowFactory.OrderBookStartupData.DefaultGrid
|
||||
};
|
||||
|
||||
[ProtoMember(1)]
|
||||
@@ -21,19 +26,53 @@ namespace mbts.spark {
|
||||
// This way, when they are enabled, there won't be any backwards compatability issues to worry about, as no clients will have persisted them yet.
|
||||
NotificationSettings _notification;
|
||||
|
||||
[ProtoMember(5)]
|
||||
GridSettings _market_view_grid;
|
||||
[ProtoMember(6)]
|
||||
GridSettings _time_and_sales_grid;
|
||||
[ProtoMember(7)]
|
||||
GridSettings _fills_grid;
|
||||
[ProtoMember(8)]
|
||||
GridSettings _synthetic_management_grid;
|
||||
[ProtoMember(9)]
|
||||
GridSettings _pnl_grid;
|
||||
[ProtoMember(10)]
|
||||
bts.shell.OrderBookWindowFactory.OrderBookGridSettings _order_book_grid;
|
||||
|
||||
public UserSettings() { }
|
||||
|
||||
public LadderSettings ladder => _ladder ?? (_ladder = LadderSettings.Default);
|
||||
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
|
||||
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
|
||||
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
|
||||
public bts.shell.OrderBookWindowFactory.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.OrderBookWindowFactory.OrderBookStartupData.Default.grid_settings_wrapper());
|
||||
|
||||
public UserSettings with(LadderSettings ladder = null, NotificationSettings notification = null) =>
|
||||
public UserSettings with(LadderSettings ladder = null,
|
||||
NotificationSettings notification = null,
|
||||
GridSettings provided_market_view_gridsettings = null,
|
||||
GridSettings time_and_sales = null,
|
||||
GridSettings provided_fills_gridsettings = null,
|
||||
GridSettings provided_synthetic_management_gridsettings = null,
|
||||
GridSettings provided_pnl_gridsettings = null,
|
||||
bts.shell.OrderBookWindowFactory.OrderBookGridSettings order_book_grid = null) =>
|
||||
new UserSettings {
|
||||
_ladder = ladder ?? _ladder,
|
||||
_notification = notification ?? _notification
|
||||
_notification = notification ?? _notification,
|
||||
_market_view_grid = provided_market_view_gridsettings ?? _market_view_grid,
|
||||
_time_and_sales_grid = time_and_sales ?? _time_and_sales_grid,
|
||||
_fills_grid = provided_fills_gridsettings ?? _fills_grid,
|
||||
_synthetic_management_grid = provided_synthetic_management_gridsettings ?? _synthetic_management_grid,
|
||||
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
|
||||
_order_book_grid = order_book_grid ?? _order_book_grid
|
||||
};
|
||||
|
||||
public static class FileExtensions {
|
||||
public const string LADDER = "ladder";
|
||||
public const string
|
||||
LADDER = "ladder",
|
||||
GRID = "grid";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ namespace mbts.persist {
|
||||
const string NAME_SPACE = "settings", DEFAULT_KEY = "default";
|
||||
IUserPersist _persist;
|
||||
UserSettings _default_settings;
|
||||
readonly Subject<UserSettings> _default_settings_subject = new Subject<UserSettings>();
|
||||
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
|
||||
readonly object _default_settings_lock = new object();
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserSettingsHub(IUserPersist persist) {
|
||||
@@ -33,7 +34,12 @@ namespace mbts.persist {
|
||||
/// Assuming there aren't any rogue agents saving UserSettings by themselves, there should never be any discrepancies.
|
||||
public IObservable<UserSettings> subscribe_default_settings() => _default_settings_subject.AsObservable();
|
||||
|
||||
internal UserSettings current => _default_settings ?? UserSettings.factory_defaults;
|
||||
internal UserSettings current {
|
||||
get {
|
||||
lock (_default_settings_lock)
|
||||
return _default_settings ?? UserSettings.factory_defaults;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task save_default(UserSettings value) {
|
||||
_set_and_notify(value);
|
||||
@@ -41,9 +47,11 @@ namespace mbts.persist {
|
||||
}
|
||||
|
||||
void _set_and_notify(UserSettings value) {
|
||||
_default_settings = value;
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
_default_settings_subject.OnNext(value);
|
||||
lock (_default_settings_lock) {
|
||||
_default_settings = value;
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
_default_settings_subject.OnNext(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
-2
@@ -1,7 +1,9 @@
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.persist {
|
||||
@@ -11,8 +13,15 @@ namespace mbts.persist {
|
||||
event Action<T> instance_settings_changed;
|
||||
IObservable<T> subscribe_default();
|
||||
Task save_default_settings(T value);
|
||||
IWindowSettingsManagerNonShared<T> as_non_shared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IWindowSettingsManager that uses a clone of the actual default settings;
|
||||
/// provides a convenient way to avoid problems that arise from sharing a single default settings object among multiple window instances.
|
||||
/// </summary>
|
||||
interface IWindowSettingsManagerNonShared<T> : IWindowSettingsManager<T>, IDisposable where T : class { }
|
||||
|
||||
class WindowSettingsManager<T> : IWindowSettingsManager<T> where T : class {
|
||||
readonly UserSettingsHub _hub;
|
||||
readonly Func<UserSettings, T> _get;
|
||||
@@ -43,6 +52,42 @@ namespace mbts.persist {
|
||||
|
||||
public IObservable<T> subscribe_default() =>
|
||||
_hub.subscribe_default_settings().Select(_get).DistinctUntilChanged();
|
||||
|
||||
public IWindowSettingsManagerNonShared<T> as_non_shared() =>
|
||||
new NonSharedWrapper(this);
|
||||
|
||||
class NonSharedWrapper : IWindowSettingsManagerNonShared<T> {
|
||||
readonly WindowSettingsManager<T> _wsm;
|
||||
readonly BehaviorSubject<T> _default_subject;
|
||||
IDisposable _dispose;
|
||||
|
||||
public NonSharedWrapper(WindowSettingsManager<T> wsm) {
|
||||
_wsm = wsm;
|
||||
_default_subject = new BehaviorSubject<T>(_wsm.default_settings ?? wsm._get(UserSettings.factory_defaults));
|
||||
_dispose = wsm.subscribe_default().Subscribe(s =>
|
||||
_default_subject.OnNext(default_settings = MiscUtils.clone_proto(s)));
|
||||
}
|
||||
|
||||
public T instance_settings {
|
||||
get => _wsm.instance_settings;
|
||||
set => _wsm.instance_settings = value;
|
||||
}
|
||||
public T default_settings { get; private set; }
|
||||
public event Action<T> instance_settings_changed {
|
||||
add => _wsm.instance_settings_changed += value;
|
||||
remove => _wsm.instance_settings_changed -= value;
|
||||
}
|
||||
public async Task save_default_settings(T value) =>
|
||||
await _wsm.save_default_settings(value);
|
||||
|
||||
public IObservable<T> subscribe_default() =>
|
||||
_default_subject.AsObservable();
|
||||
|
||||
public IWindowSettingsManagerNonShared<T> as_non_shared() =>
|
||||
new NonSharedWrapper(_wsm);
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
|
||||
static class WindowSettingsManagerExtensions {
|
||||
@@ -54,8 +99,7 @@ namespace mbts.persist {
|
||||
Observable.CombineLatest(
|
||||
Observable.FromEvent<Action<T>, T>(cb => wsm.instance_settings_changed += cb, cb => wsm.instance_settings_changed -= cb)
|
||||
.StartWith(wsm.instance_settings),
|
||||
wsm.subscribe_default()
|
||||
.StartWith(wsm.default_settings),
|
||||
wsm.subscribe_default(),
|
||||
(i, d) => i ?? d
|
||||
).Where(v => v != null)
|
||||
.DistinctUntilChanged();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
@@ -14,16 +13,13 @@ namespace mbts.utils {
|
||||
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
int i = 0;
|
||||
foreach (var val in src.empty_if_null()) {
|
||||
int curr_index = dest.TakeWhile(dval => !comp.Equals(dval, val)).Count();
|
||||
if (curr_index == dest.Count) {
|
||||
dest.Add(val);
|
||||
} else if (curr_index == i) {
|
||||
int curr_index = dest.IndexOf(val);
|
||||
if (curr_index == i) {
|
||||
continue;
|
||||
} else if (curr_index < dest.Count) {
|
||||
} else if (curr_index > 0 && curr_index < dest.Count) {
|
||||
dest.RemoveAt(curr_index);
|
||||
dest.Insert(i, val);
|
||||
}
|
||||
++i;
|
||||
dest.Insert(i++, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,5 +45,12 @@ namespace mbts.utils {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
public static TResult[] convert_array<T, TResult>(T[] source, Func<T, TResult> selector) {
|
||||
TResult[] rv = new TResult[source.Length];
|
||||
for (int i = 0; i < rv.Length; i++)
|
||||
rv[i] = selector(source[i]);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using bts;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IPrepareUpdateForDispatcher {
|
||||
IDisposable prepare_update();
|
||||
}
|
||||
|
||||
class DispatcherUpdateMaster : IDisposable {
|
||||
readonly string _name;
|
||||
readonly DispatcherPriority _invocation_priority;
|
||||
SetMap<Dispatcher, IPrepareUpdateForDispatcher> _vms;
|
||||
int _state = States.ReadyForUpdate;
|
||||
IDisposable _dispose;
|
||||
|
||||
public DispatcherUpdateMaster(string name)
|
||||
: this(name, WorkLoop.default_interval, WorkLoop.default_priority) { }
|
||||
|
||||
public DispatcherUpdateMaster(string name, TimeSpan period, DispatcherPriority invocation_priority) {
|
||||
_name = name;
|
||||
_vms = new SetMap<Dispatcher, IPrepareUpdateForDispatcher>(EqualityComparer.Create<Dispatcher, int>(d => d.Thread.ManagedThreadId));
|
||||
_dispose = ThreadPoolScheduler.Instance.SchedulePeriodic(period, _update_async);
|
||||
_invocation_priority = invocation_priority;
|
||||
}
|
||||
|
||||
public IDisposable attach(IPrepareUpdateForDispatcher vm, Dispatcher dispatcher) {
|
||||
var clone = _vms.clone();
|
||||
clone.add(dispatcher, vm);
|
||||
_vms = clone;
|
||||
return Disposable.Create(() => _detach(vm, dispatcher));
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Thread.VolatileWrite(ref _state, States.Disposed);
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
|
||||
void _detach(IPrepareUpdateForDispatcher preper, Dispatcher dispatcher) {
|
||||
var clone = _vms.clone();
|
||||
clone.remove(dispatcher, preper);
|
||||
_vms = clone;
|
||||
}
|
||||
|
||||
async void _update_async() {
|
||||
if (Interlocked.CompareExchange(ref _state, States.UpdateInProgress, States.ReadyForUpdate) != States.ReadyForUpdate) {
|
||||
return;
|
||||
}
|
||||
var temp = _vms;
|
||||
try {
|
||||
ConcurrentBag<(Dispatcher dispatcher, ConcurrentBag<IDisposable> update)> update_jobs =
|
||||
new ConcurrentBag<(Dispatcher dispatcher, ConcurrentBag<IDisposable> update_jobs)>();
|
||||
await Task.Run(() => {
|
||||
Parallel.ForEach(temp, dispatcher_group => {
|
||||
ConcurrentBag<IDisposable> update = new ConcurrentBag<IDisposable>();
|
||||
Parallel.ForEach(dispatcher_group, vm => {
|
||||
// need to check for null, that indicates an empty update (rather than an actual empty enumerable)
|
||||
var ujs = vm.prepare_update();
|
||||
if (ujs != null)
|
||||
update.Add(ujs);
|
||||
});
|
||||
if (update.Count > 0)
|
||||
update_jobs.Add((dispatcher_group.Key, update));
|
||||
});
|
||||
});
|
||||
await Task.WhenAll(update_jobs.Select(_update_dispatcher_group_async));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"{_name} error during parallel update: {ex}");
|
||||
} finally {
|
||||
// signal that the update is complete, and we are ready for another
|
||||
Interlocked.CompareExchange(ref _state, States.ReadyForUpdate, States.UpdateInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
async Task _update_dispatcher_group_async((Dispatcher dispatcher, ConcurrentBag<IDisposable> update_jobs) t) =>
|
||||
await t.dispatcher.InvokeAsync(() => t.update_jobs.DisposeAll(), _invocation_priority);
|
||||
|
||||
static class States {
|
||||
public const int
|
||||
ReadyForUpdate = 0,
|
||||
UpdateInProgress = 1,
|
||||
Disposed = 2;
|
||||
}
|
||||
}
|
||||
|
||||
static class DispatcherUpdateMasterExtensions {
|
||||
public static IDisposable attach(this DispatcherUpdateMaster dum, IPrepareUpdateForDispatcher preparer) =>
|
||||
dum.attach(preparer, Dispatcher.CurrentDispatcher);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@ namespace mbts.utils {
|
||||
/// <seealso cref="Observable.SelectMany"/>
|
||||
class KeyDrivenObservableSeries<TKey, TValue> : ISubject<TKey, TValue>, IDisposable {
|
||||
readonly Func<TKey, IObservable<TValue>> _selector;
|
||||
object _lock = new object();
|
||||
readonly object _lock = new object();
|
||||
bool _is_disposed;
|
||||
Subject<TValue> _value_subject = new Subject<TValue>();
|
||||
IDisposable _current_key_sub;
|
||||
|
||||
@@ -31,15 +32,19 @@ namespace mbts.utils {
|
||||
// need to lock bc simultaneous callbacks on diff threads could generate 2 subscriptions at once,
|
||||
// only 1 of which would get disposed in the next callback.
|
||||
Disposable.dispose(ref _current_key_sub);
|
||||
_current_key_sub = _selector(key)?.Subscribe(_value_subject);
|
||||
if (!_is_disposed)
|
||||
_current_key_sub = _selector(key)?.Subscribe(_value_subject);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TValue> observer) => _value_subject.Subscribe(observer);
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _current_key_sub);
|
||||
Disposable.dispose(ref _value_subject);
|
||||
lock (_lock) {
|
||||
_is_disposed = true;
|
||||
Disposable.dispose(ref _current_key_sub);
|
||||
Disposable.dispose(ref _value_subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,15 +71,73 @@ namespace mbts.utils {
|
||||
|
||||
class AnonymousConnectable<TResult> : IConnectableObservable<TResult> {
|
||||
readonly IObservable<TSource> _source;
|
||||
readonly KeyDrivenObservableSeries<TSource, TResult> _driver;
|
||||
readonly Func<TSource, IObservable<TResult>> _select_next;
|
||||
List<LazySubscription> _lazy_subs = new List<LazySubscription>();
|
||||
KeyDrivenObservableSeries<TSource, TResult> _driver;
|
||||
IDisposable _driver_sub;
|
||||
bool _is_connected = false;
|
||||
|
||||
public AnonymousConnectable(IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) {
|
||||
_source = source;
|
||||
_driver = new KeyDrivenObservableSeries<TSource, TResult>(select_next);
|
||||
_select_next = select_next;
|
||||
}
|
||||
|
||||
public IDisposable Connect() => _source.Subscribe(_driver);
|
||||
public IDisposable Subscribe(IObserver<TResult> observer) => _driver.Subscribe(observer);
|
||||
// idempotent
|
||||
public IDisposable Connect() {
|
||||
lock (this) {
|
||||
if (!_is_connected) {
|
||||
_driver = new KeyDrivenObservableSeries<TSource, TResult>(_select_next);
|
||||
foreach (var ls in _lazy_subs)
|
||||
ls.attach();
|
||||
_lazy_subs.Clear();
|
||||
_driver_sub = _source.Subscribe(_driver);
|
||||
_is_connected = true;
|
||||
}
|
||||
}
|
||||
return Disposable.Create(_deconnect);
|
||||
}
|
||||
|
||||
void _deconnect() {
|
||||
lock (this) {
|
||||
if (_is_connected) {
|
||||
_driver_sub.Dispose();
|
||||
_driver.Dispose();
|
||||
_is_connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TResult> observer) {
|
||||
lock (this) {
|
||||
if (_is_connected)
|
||||
return _driver.Subscribe(observer);
|
||||
var ls = new LazySubscription(this, observer);
|
||||
_lazy_subs.Add(ls);
|
||||
return ls;
|
||||
}
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
class LazySubscription : IDisposable {
|
||||
readonly IObserver<TResult> _observer;
|
||||
readonly AnonymousConnectable<TResult> _owner;
|
||||
IDisposable _sub;
|
||||
|
||||
public LazySubscription(AnonymousConnectable<TResult> owner, IObserver<TResult> observer) {
|
||||
_owner = owner;
|
||||
_observer = observer;
|
||||
}
|
||||
|
||||
public void attach() =>
|
||||
_sub = _owner._driver.Subscribe(_observer);
|
||||
|
||||
public void Dispose() {
|
||||
lock (_owner) {
|
||||
Disposable.dispose(ref _sub);
|
||||
_owner._lazy_subs.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace mbts.utils {
|
||||
/// <summary>
|
||||
/// Facilities multi-threaded read/write operations to a particular <typeparamref name="T"/> reference.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The reference type to be read/written.</typeparam>
|
||||
class RefValueContainer<T> where T : class {
|
||||
readonly object _lock = new object();
|
||||
T _value;
|
||||
bool _is_dirty;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current value if it has not been read already, otherwise returns null.
|
||||
/// </summary>
|
||||
public T read_new_value() {
|
||||
lock (_lock) {
|
||||
if (_is_dirty) {
|
||||
_is_dirty = false;
|
||||
return _value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void write_value(T value) {
|
||||
lock (_lock) {
|
||||
_is_dirty = true;
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void mark_dirty() {
|
||||
lock (_lock)
|
||||
_is_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ namespace mbts.ui.shell
|
||||
#endregion
|
||||
|
||||
#region automatic property
|
||||
protected delegate void PropertyChange<T>(T oldval, T newval);
|
||||
protected internal delegate void PropertyChange<T>(T oldval, T newval);
|
||||
|
||||
// use nameof() to add compile time checks
|
||||
protected Property<T> backing<T>(string name, T default_value = default(T)) =>
|
||||
@@ -156,13 +156,15 @@ namespace mbts.ui.shell
|
||||
public string name => _propname;
|
||||
|
||||
public void set(T value) {
|
||||
if (!Equals(value, _value))
|
||||
_apply_new_value(value);
|
||||
if (!Equals(value, _value)) {
|
||||
Changing?.Invoke(_value, value);
|
||||
var old = _value;
|
||||
_value = value;
|
||||
Changed?.Invoke(old, value);
|
||||
_owner.RaisePropertyChanged(_propname);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable set_as_job(T value) =>
|
||||
Equals(value, _value) ? null : Disposable.Create(() => _apply_new_value(value));
|
||||
|
||||
public IDisposable Subscribe(IObserver<(T oldval, T newval)> observer) {
|
||||
Changed += pch;
|
||||
return Disposable.Create(() => Changed -= pch);
|
||||
@@ -180,14 +182,6 @@ namespace mbts.ui.shell
|
||||
get { return _value; }
|
||||
set { set(value); }
|
||||
}
|
||||
|
||||
void _apply_new_value(T new_value) {
|
||||
Changing?.Invoke(_value, new_value);
|
||||
var old = _value;
|
||||
_value = new_value;
|
||||
Changed?.Invoke(old, new_value);
|
||||
_owner.RaisePropertyChanged(_propname);
|
||||
}
|
||||
}
|
||||
|
||||
protected class DelegateProperty<T> : Property<T> {
|
||||
@@ -452,9 +446,6 @@ namespace mbts.ui.shell
|
||||
public IEnumerable<string> AllErrors { get { return _errors.Values; } }
|
||||
#endregion
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, bool validate_now = false, params Func<T, bool>[] validation_steps) =>
|
||||
backing(name, default_value, validation_steps.Select(v => new PropertyValidation<T>(v)).ToLArray(), validate_now);
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, IEnumerable<PropertyValidation<T>> validation_steps, bool validate_now = false) {
|
||||
void run_validation(T val) {
|
||||
foreach (var pv in validation_steps) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.proto {
|
||||
[ProtoContract]
|
||||
struct ColorShowProto : IEquatable<ColorShowProto> {
|
||||
[ProtoMember(1)]
|
||||
readonly uint _value;
|
||||
[ProtoMember(2)]
|
||||
readonly bool _show;
|
||||
|
||||
public ColorShowProto(Color value, bool show = true)
|
||||
: this(value.rgb_color(), show) { }
|
||||
|
||||
public ColorShowProto(uint value, bool show = true) {
|
||||
_value = value;
|
||||
_show = show;
|
||||
}
|
||||
|
||||
public Color color => _value.rgb_color();
|
||||
public bool show => _show;
|
||||
|
||||
public Color? result {
|
||||
get {
|
||||
if (_show)
|
||||
return color;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ColorShowProto other) =>
|
||||
_value == other._value &&
|
||||
_show == other._show;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ColorShowProto ncp && Equals(ncp);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(_value.GetHashCode(), _show.GetHashCode());
|
||||
|
||||
public static bool operator ==(ColorShowProto a, ColorShowProto b) => a.Equals(b);
|
||||
public static bool operator !=(ColorShowProto a, ColorShowProto b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace mbts.spark {
|
||||
value = initial_value;
|
||||
}
|
||||
|
||||
public event Action<(T oldval, T newval)> value_set;
|
||||
protected event Action<(T oldval, T newval)> _value_set;
|
||||
|
||||
public T value { get; private set; }
|
||||
public string text => _text ?? (_text = _format(value));
|
||||
@@ -27,22 +27,14 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable on_next_distinct_as_job(T value, ref bool dirty) {
|
||||
if (!value.Equals(this.value)) {
|
||||
dirty = true;
|
||||
return Disposable.Create(() => OnNext(value));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public void OnNext(T value) {
|
||||
if (value_set != null) {
|
||||
if (_value_set != null) {
|
||||
T prev = this.value;
|
||||
this.value = value;
|
||||
_text = null;
|
||||
value_set.Invoke((prev, value));
|
||||
_value_set.Invoke((prev, value));
|
||||
} else {
|
||||
this.value = value;
|
||||
_text = null;
|
||||
@@ -50,7 +42,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<(T oldval, T newval)> observer) =>
|
||||
Observable.FromEvent<Action<(T oldval, T newval)>, (T oldval, T newval)>(cb => value_set += cb, cb => value_set -= cb)
|
||||
Observable.FromEvent<Action<(T oldval, T newval)>, (T oldval, T newval)>(cb => _value_set += cb, cb => _value_set -= cb)
|
||||
.Subscribe(observer);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace mbts.ui.controls {
|
||||
bool _is_mouse_over_text, _can_execute;
|
||||
Pen _underline_pen;
|
||||
|
||||
static HyperlinkCell() =>
|
||||
static HyperlinkCell() {
|
||||
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
|
||||
HorizontalAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
@@ -33,6 +33,13 @@ namespace mbts.ui.controls {
|
||||
o._horizontal_alignment = v;
|
||||
o._glyph_run = null;
|
||||
}, HorizontalAlignment.Left);
|
||||
WPFHelper.override_metadata<double, HyperlinkCell>(
|
||||
WidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._glyph_run = null);
|
||||
}
|
||||
|
||||
public HyperlinkCell() {
|
||||
_background = Background;
|
||||
@@ -279,24 +286,50 @@ namespace mbts.ui.controls {
|
||||
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
|
||||
if (_is_mouse_over_text) {
|
||||
// draw underline
|
||||
var pt = new Point(Padding.Left, 0.5 * (ActualHeight + _font_size) + 1);
|
||||
Point pt;
|
||||
switch (_horizontal_alignment) {
|
||||
case HorizontalAlignment.Left:
|
||||
pt = new Point(Padding.Left, 0.5 * (ActualHeight + _font_size) + 1);
|
||||
break;
|
||||
case HorizontalAlignment.Right:
|
||||
pt = new Point(ActualWidth - Padding.Right - _glyph_width, 0.5 * (ActualHeight + _font_size) + 1);
|
||||
break;
|
||||
case HorizontalAlignment.Center:
|
||||
default:
|
||||
pt = new Point(0.5 * (ActualWidth - _glyph_width), 0.5 * (ActualHeight + _font_size) + 1);
|
||||
break;
|
||||
}
|
||||
drawingContext.DrawLine(_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
|
||||
|
||||
}
|
||||
drawingContext.Pop();
|
||||
}
|
||||
var bgo = GetBackgroundOverlay(this);
|
||||
if (bgo != null)
|
||||
drawingContext.DrawRectangle(bgo, null, rect);
|
||||
if (bg != null) {
|
||||
var bgo = GetBackgroundOverlay(this);
|
||||
if (bgo != null)
|
||||
drawingContext.DrawRectangle(bgo, null, rect);
|
||||
}
|
||||
}
|
||||
|
||||
bool _point_horizontally_over_text(double x) {
|
||||
switch (_horizontal_alignment) {
|
||||
case HorizontalAlignment.Left:
|
||||
return x >= _padding.Left && x <= _padding.Left + _glyph_width;
|
||||
case HorizontalAlignment.Right:
|
||||
return x >= ActualWidth - Padding.Right - _glyph_width && x <= ActualWidth - _padding.Right;
|
||||
case HorizontalAlignment.Center:
|
||||
default:
|
||||
return x >= 0.5 * (ActualWidth - _glyph_width) && x <= 0.5 * (ActualWidth - _glyph_width) + _glyph_width;
|
||||
}
|
||||
}
|
||||
|
||||
void _check_mouse_over_text(MouseEventArgs e) {
|
||||
var pt = e.GetPosition(this);
|
||||
bool over_text =
|
||||
_glyph_run != null &&
|
||||
pt.X >= _padding.Left &&
|
||||
pt.X <= _padding.Left + _glyph_width &&
|
||||
pt.Y >= _padding.Top &&
|
||||
pt.Y <= _padding.Top + _font_size;
|
||||
_glyph_run != null &&
|
||||
_point_horizontally_over_text(pt.X) &&
|
||||
pt.Y >= _padding.Top &&
|
||||
pt.Y <= _padding.Top + _font_size;
|
||||
if (_is_mouse_over_text != over_text)
|
||||
_toggle_mouse_over_text();
|
||||
}
|
||||
|
||||
@@ -16,22 +16,40 @@ namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
public static class DefaultColumnWidths {
|
||||
public const double
|
||||
Account = 68,
|
||||
Exchange = 74,
|
||||
Account = 80,
|
||||
Exchange = 86,
|
||||
FillKey = 68,
|
||||
OrderKey = 78,
|
||||
OrderType = 88,
|
||||
Price = 56,
|
||||
MViewPrice = 56,
|
||||
Price = 68,
|
||||
AveragePrice = 76,
|
||||
LastPrice = 78,
|
||||
Settlement = 82,
|
||||
Security = 80,
|
||||
SecurityKey = 90,
|
||||
SecurityType = 96,
|
||||
Side = 47,
|
||||
Size = 48,
|
||||
Symbol = 48,
|
||||
Time = 100,
|
||||
ImpliedDirectMergedSize = 68,
|
||||
Filled = 56,
|
||||
Symbol = 78,
|
||||
Time = 112,
|
||||
TimeInForce = 47,
|
||||
TradeKey = 76,
|
||||
User = 53,
|
||||
Aggressor = 80;
|
||||
Aggressor = 80,
|
||||
Volume = 74,
|
||||
FillKind = 70,
|
||||
Description = 300,
|
||||
N_Legs = 68,
|
||||
N_Working_Orders = 123,
|
||||
Start_Net_Pos = 100,
|
||||
Total_Net_Pos = 100,
|
||||
PnL_Generic = 80,
|
||||
Working = 72,
|
||||
LastMessage = 100,
|
||||
ParentKey = 100;
|
||||
}
|
||||
|
||||
public static class DefaultColumnHeaders {
|
||||
@@ -45,14 +63,41 @@ namespace mbts.ui.controls {
|
||||
Price = "Price",
|
||||
Security = "Security",
|
||||
SecurityKey = "Security Key",
|
||||
SecurityType = "Security Type",
|
||||
Side = "Side",
|
||||
Size = "Qty",
|
||||
MergedSize = "Qty (M)",
|
||||
DirectSize = "Qty (D)",
|
||||
ImpliedSize = "Qty (I)",
|
||||
Symbol = "Symbol",
|
||||
TimeInForce = "TIF",
|
||||
TradeKey = "Trade Key",
|
||||
User = "User",
|
||||
Time = "Time",
|
||||
Aggressor = "Aggressor";
|
||||
Aggressor = "Aggressor",
|
||||
Volume = "Volume",
|
||||
Last = "Last",
|
||||
Pos = "Pos",
|
||||
Working = "Working",
|
||||
Bid = "Bid",
|
||||
Ask = "Ask",
|
||||
Settlement = "Settlement",
|
||||
DayChange = "Change",
|
||||
SessionLow = "Low",
|
||||
SessionHigh = "High",
|
||||
FillKind = "Fill Kind",
|
||||
Name = "Name",
|
||||
Description = "Description",
|
||||
N_Legs = "# Legs",
|
||||
N_Working_Orders = "# Working Orders",
|
||||
StartNetPos = "Start Net Pos",
|
||||
StartPnL = "Start PnL",
|
||||
DayLong = "Day Long",
|
||||
DayShort = "Day Short",
|
||||
DayPnL = "Day PnL",
|
||||
TotalNetPos = "Total Net Pos",
|
||||
TotalPnL = "Total PnL",
|
||||
PnLPrice = "PnL Price";
|
||||
}
|
||||
|
||||
public interface IKeyedColumn<TColKey> {
|
||||
@@ -158,7 +203,7 @@ namespace mbts.ui.controls {
|
||||
public virtual void load_from(GridColumnSettings settings) {
|
||||
if (settings == null)
|
||||
return;
|
||||
using (ReflectionHelper.detect_property_changes(this, raise_prop_changed, nameof(is_visible), nameof(is_collapsed), nameof(font_weight_override), nameof(font_weight),
|
||||
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(is_visible), nameof(is_collapsed), nameof(font_weight_override), nameof(font_weight),
|
||||
nameof(font_color_override), nameof(font_color), nameof(font_brush), nameof(font_family), nameof(font_size), nameof(horizontal_alignment), nameof(horizontal_offset))) {
|
||||
//
|
||||
id = settings.id;
|
||||
@@ -176,21 +221,24 @@ namespace mbts.ui.controls {
|
||||
Width = original_width;
|
||||
}
|
||||
//
|
||||
void raise_prop_changed(string prop) =>
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
}
|
||||
|
||||
// 'fallback' font-params (aka 'grid-level' font-params)
|
||||
public void set_fallback_font_params(FontFamily font_family, double font_size, Color font_color, FontWeight font_weight) {
|
||||
_fallback_font_family = font_family;
|
||||
_fallback_font_size = font_size;
|
||||
_fallback_font_color = font_color;
|
||||
_fallback_font_weight = font_weight;
|
||||
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(font_family), nameof(font_size), nameof(font_color), nameof(font_weight))) {
|
||||
_fallback_font_family = font_family;
|
||||
_fallback_font_size = font_size;
|
||||
_fallback_font_color = font_color;
|
||||
_fallback_font_weight = font_weight;
|
||||
}
|
||||
font_params_changed?.Invoke();
|
||||
}
|
||||
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
|
||||
sort_direction_changed?.Invoke(this, new_value);
|
||||
|
||||
void _raise_prop_changed(string prop) =>
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
}
|
||||
|
||||
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IGridColumn {
|
||||
|
||||
+5
-1
@@ -29,9 +29,13 @@ namespace mbts.ui.controls {
|
||||
background_overlay = selection_fill;
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var hl = new HyperlinkCell { Foreground = font_brush, Background = background, FontSize = font_size, Padding = padding };
|
||||
var hl = new HyperlinkCell { Foreground = font_brush, Background = background, Padding = padding };
|
||||
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.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
|
||||
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
if (background_overlay != null) {
|
||||
hl.SetBinding(BackgroundOverlayProperty, new Binding(nameof(ListBoxItem.IsSelected)) {
|
||||
Converter = new AnonymousValueConverter<bool, Brush>(val => val ? background_overlay : null),
|
||||
|
||||
+3
-1
@@ -22,8 +22,10 @@ namespace mbts.ui.controls {
|
||||
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var v = new TextCell { FontFamily = font_family, FontSize = font_size, Foreground = font_brush, HorizontalAlignment = horizontal_alignment, VerticalAlignment = vertical_alignment };
|
||||
var v = new TextCell { Foreground = font_brush, HorizontalAlignment = horizontal_alignment, 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 });
|
||||
try_set_binding(TextCell.BackgroundProperty, background_binding);
|
||||
try_set_binding(TextCell.ForegroundProperty, foreground_binding);
|
||||
try_set_binding(TextCell.FontWeightProperty, font_weight_binding);
|
||||
|
||||
+11
-6
@@ -1,21 +1,26 @@
|
||||
using mbts.spark;
|
||||
using bts.shell;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row> {
|
||||
|
||||
readonly Func<TimeAndSalesVM.Row, string> _get_text;
|
||||
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row>, IKeyedColumn<TimeAndSalesView.Columns> {
|
||||
readonly Func<TimeAndSalesVM.Row, Brush> _get_brush;
|
||||
|
||||
public TimeAndSalesColumn(Func<TimeAndSalesVM.Row, string> get_text, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null, GlyphContext glyph_context = null) : base(get_text, filter, filter_template, glyph_context) {
|
||||
_get_text = get_text;
|
||||
public TimeAndSalesColumn(TimeAndSalesView.Columns key, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null)
|
||||
: base(r => r[key], filter, filter_template) {
|
||||
this.key = key;
|
||||
// id is used for settings (another case of ProtoBufs vs Generics - strings seemed like the easiest way to provide an identifier for the common GridColumnSettings proto)
|
||||
id = TimeAndSalesWindowFactory.col_id(key);
|
||||
_get_brush = get_brush;
|
||||
}
|
||||
|
||||
public TimeAndSalesView.Columns key { get; }
|
||||
|
||||
public override void draw(TimeAndSalesVM.Row row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc
|
||||
, glyph_context
|
||||
|
||||
@@ -20,18 +20,20 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
public class RenderTextColumn<TRowVM> : RenderColumnBase<TRowVM>, IRenderColumn<TRowVM>, IFilterableColumn {
|
||||
readonly Func<TRowVM, string> _get_text;
|
||||
protected readonly Func<TRowVM, string> _get_text;
|
||||
|
||||
public RenderTextColumn(
|
||||
Func<TRowVM, string> get_text,
|
||||
IDataGridColumnFilter filter = null,
|
||||
string filter_template = null,
|
||||
GlyphContext glyph_context = null)
|
||||
string filter_template = null)
|
||||
: base() {
|
||||
_get_text = get_text;
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
this.glyph_context = glyph_context ?? TextUtils.StandardGlyphContext;
|
||||
font_params_changed += refresh_glyph_context;
|
||||
refresh_glyph_context();
|
||||
void refresh_glyph_context() =>
|
||||
glyph_context = GlyphContext.create(font_family, font_weight);
|
||||
}
|
||||
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
@@ -39,7 +41,7 @@ namespace mbts.ui.controls {
|
||||
public Brush background { get; set; }
|
||||
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
|
||||
public Thickness padding { get; set; } = DefaultCellPadding;
|
||||
public GlyphContext glyph_context { get; set; } = TextUtils.StandardGlyphContext;
|
||||
public GlyphContext glyph_context { get; private set; }
|
||||
|
||||
public override void draw(TRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace mbts.ui.controls {
|
||||
o.Resources[ActiveSelectionHighlightKey] = v;
|
||||
o.Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(v.Color) { Opacity = 0.2 }.frozen();
|
||||
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_brush().frozen(), 1).frozen();
|
||||
foreach (var col in o.Columns.OfType<ISelectionHighlightListener>())
|
||||
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
|
||||
col.on_selection_fill(v);
|
||||
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
|
||||
DefaultSelectionHighlight);
|
||||
@@ -634,7 +634,7 @@ namespace mbts.ui.controls {
|
||||
(o, v) => {
|
||||
if (DoubleHelper.IsNaNOrInfinity(v))
|
||||
v = 0;
|
||||
if (!o._visible_columns.is_null_or_empty()) {
|
||||
if (!o._visible_columns.is_null_or_empty() && o.Columns != null) {
|
||||
double max_font_size = o.Columns.Max(c => c.font_size);
|
||||
double min_row_height = o.RowPadding.Top + max_font_size + o.RowPadding.Bottom;
|
||||
if (v < min_row_height)
|
||||
|
||||
@@ -335,6 +335,8 @@
|
||||
<ScrollBar Grid.Row="3" Grid.Column="1" Orientation="Horizontal" LargeChange="10" x:Name="_scroll_horiz" />
|
||||
|
||||
<!-- todo: All edit controls go in a canvas - edit api is called with an edit type. Hide all controls -->
|
||||
<!-- This stole hours of my life. The Text binding down there used to be 'value' and I was encountering -->
|
||||
<!-- another bug, but this thing sent me down a rabbit hole. F you TickUpDown -->
|
||||
<bts:TickUpDown Grid.RowSpan="3" Grid.ColumnSpan="2" Width="0" Height="0" HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
TickUpCommand="{Binding tick_up}" TickDownCommand="{Binding tick_down}"
|
||||
x:Name="_edit" Visibility="Collapsed">
|
||||
|
||||
+6
-2
@@ -28,7 +28,7 @@ namespace mbts.ui.controls {
|
||||
new AnonymousValueConverter(obj =>
|
||||
obj is SoundNotification.SystemSound s ?
|
||||
EnumHelper.get_attribute<SoundNotification.SystemSound, LicenseAttribute>(s)?.link : null);
|
||||
|
||||
|
||||
public static readonly DependencyProperty BusProperty =
|
||||
WPFHelper.create_dp<IBus, SoundAnimationView>(nameof(Bus));
|
||||
|
||||
@@ -38,7 +38,11 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
public static ICommand PreviewSoundCommand =>
|
||||
new DelegateCommand<SoundNotification.SystemSound>(s => NAudioPlayer.create(s).play());
|
||||
new DelegateCommand<SoundNotification.SystemSound>(s => {
|
||||
try {
|
||||
NAudioPlayer.create(s).play();
|
||||
} catch (NAudio.MmException) { }
|
||||
});
|
||||
|
||||
#region SoundTypes
|
||||
public static readonly DependencyProperty SoundTypesProperty =
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace mbts.ui.settings {
|
||||
|
||||
public GridColumnSettings with(
|
||||
string display_name = null, bool? is_visible = null, bool? is_collapsed = null, NullableFontWeightProto? font_weight_override = null, NullableColorProto? font_color_override = null,
|
||||
double? width = _default_width, HorizontalAlignment? horizontal_alignment = _default_horizontal_alignment, double? horizontal_offset = null) =>
|
||||
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null) =>
|
||||
//
|
||||
new GridColumnSettings(_id,
|
||||
display_name ?? this.display_name,
|
||||
|
||||
@@ -4,7 +4,6 @@ using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -28,22 +27,15 @@ namespace mbts.spark {
|
||||
ImmutableFontWeight
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateBuilder> {
|
||||
public interface IColumnVM : IValueFactory<(GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations)>, IDisposable {
|
||||
partial class GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> {
|
||||
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations)>, IDisposable {
|
||||
ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
|
||||
string id { get; }
|
||||
string display_name { get; set; }
|
||||
double width { get; set; }
|
||||
bool is_visible { get; set; }
|
||||
bool is_font_color_overridden { get; set; }
|
||||
ColorPickerPopupVM font_color_override { get; }
|
||||
bool is_font_weight_overridden { get; set; }
|
||||
FontWeight font_weight_override { get; set; }
|
||||
void apply_flags(GridColumnSettingsFeatureFlags flags);
|
||||
new (GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) create();
|
||||
new void load_from((GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) args);
|
||||
}
|
||||
|
||||
public class ColumnVM : DisposableViewModelBase, IColumnVM {
|
||||
public readonly GridSettingsVM<TRowData, TPredicateBuilder> owner;
|
||||
public readonly GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> owner;
|
||||
|
||||
readonly Property<string> _id, _display_name;
|
||||
readonly Property<double> _width;
|
||||
@@ -52,7 +44,7 @@ namespace mbts.spark {
|
||||
readonly Property<FontWeight> _font_weight_override;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public ColumnVM(GridSettingsVM<TRowData, TPredicateBuilder> owner) {
|
||||
public ColumnVM(GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> owner) {
|
||||
this.owner = owner;
|
||||
_id = backing(nameof(id), string.Empty);
|
||||
_display_name = backing(nameof(display_name), string.Empty);
|
||||
@@ -107,6 +99,7 @@ namespace mbts.spark {
|
||||
_horizontal_alignment.set(_settings.horizontal_alignment);
|
||||
_is_visible.set(_settings.is_visible);
|
||||
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
|
||||
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override.set(_settings.font_weight_override.Value);
|
||||
}
|
||||
@@ -133,6 +126,12 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public override string ToString() => $"{display_name} ({id})";
|
||||
|
||||
GridColumnSettings IValueFactory<GridColumnSettings>.create() =>
|
||||
create().settings;
|
||||
|
||||
void IValueFactory<GridColumnSettings>.load_from(GridColumnSettings value) =>
|
||||
load_from((value, new Dictionary<TPredicateBuilder, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
|
||||
}
|
||||
|
||||
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateBuilder predicate, IEnumerable<CellAnimation> animations)> {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
@@ -10,7 +12,7 @@ using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.settings {
|
||||
[ProtoContract]
|
||||
class GridSettings {
|
||||
class GridSettings : IEquatable<GridSettings> {
|
||||
const string _default_font_family = "Segoe UI";
|
||||
const double _default_font_size = 11;
|
||||
const uint _default_font_color = 0;
|
||||
@@ -94,6 +96,7 @@ namespace mbts.ui.settings {
|
||||
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
|
||||
public ColorOpacityProto selection_highlight => _selection_highlight;
|
||||
public IReadOnlyList<GridColumnSettings> columns => _columns;
|
||||
[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;
|
||||
|
||||
public static GridSettings get_default() =>
|
||||
@@ -110,5 +113,32 @@ namespace mbts.ui.settings {
|
||||
sorting_criterion ?? _sorting_criterion);
|
||||
|
||||
public GridSettings clone() => with();
|
||||
|
||||
public bool Equals(GridSettings other) =>
|
||||
other != null &&
|
||||
font_family == other.font_family &&
|
||||
font_size == other.font_size &&
|
||||
font_weight == other.font_weight &&
|
||||
font_color == other.font_color &&
|
||||
selection_highlight == other.selection_highlight &&
|
||||
EqualityComparer.safe_sequence_equals(columns, other.columns) &&
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
EqualityComparer.safe_sequence_equals(sorting_criterion, other.sorting_criterion);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as GridSettings);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
font_family.GetHashCode(),
|
||||
font_size.GetHashCode(),
|
||||
font_weight.GetHashCode(),
|
||||
font_color.GetHashCode(),
|
||||
selection_highlight.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(columns),
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
EqualityComparer.safe_aggregate_hash(sorting_criterion));
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
@@ -17,10 +18,10 @@ using System.Windows.Media;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.ui {
|
||||
class GridSettingsManager<TRow, TRowData, TPredicateBuilderArg, TPredicateBuilder> :
|
||||
ViewModelBase, IValueFactory<(GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data)>, IDisposable
|
||||
class GridSettingsManager<TRow, TRowData, TPredicateBuilderArg, TPredicateBuilder, TSettingsData> : ViewModelBase, IValueFactory<TSettingsData>, IObserver<TSettingsData>, IDisposable
|
||||
where TRow : IRowVM<TRowData>
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData, TPredicateBuilderArg>, new() {
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData, TPredicateBuilderArg>, new()
|
||||
where TSettingsData : class, IGridSettingsWithNotifications<TRowData, TPredicateBuilder> {
|
||||
//
|
||||
public readonly ObservableCollection<string> live_sorting_props;
|
||||
|
||||
@@ -31,7 +32,7 @@ namespace mbts.ui {
|
||||
readonly ObservableCollection<TRow> _rows;
|
||||
readonly ICollectionView _rows_view;
|
||||
readonly ObservableCollection<ColumnBase> _columns;
|
||||
readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
//readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
@@ -39,12 +40,18 @@ namespace mbts.ui {
|
||||
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight;
|
||||
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
|
||||
readonly TPredicateBuilderArg _arg;
|
||||
readonly Func<IPropertyWrapperVM<TPredicateBuilder>> _create_predicate_vm;
|
||||
readonly Func<string, IPropertyWrapperVM<TPredicateBuilder>> _create_column_predicate_vm;
|
||||
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> _create_settings_obj;
|
||||
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
|
||||
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> _predicate_animations_data;
|
||||
|
||||
public GridSettingsManager(
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel orders_model, ObservableCollection<TRow> rows,
|
||||
ICollectionView rows_view, ObservableCollection<ColumnBase> columns, Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm,
|
||||
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm, TPredicateBuilderArg arg, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm,
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> create_settings_obj,
|
||||
TPredicateBuilderArg arg, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
//
|
||||
_bus = bus;
|
||||
_notification_service = notification_service;
|
||||
@@ -52,30 +59,34 @@ namespace mbts.ui {
|
||||
_rows = rows;
|
||||
_rows_view = rows_view;
|
||||
_columns = columns;
|
||||
_create_predicate_vm = create_predicate_vm;
|
||||
_create_column_predicate_vm = create_column_predicate_vm;
|
||||
_create_settings_obj = create_settings_obj;
|
||||
_arg = arg;
|
||||
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
|
||||
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
|
||||
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
|
||||
_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.to_brush().frozen(), nameof(grid_settings));
|
||||
_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
//_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
_resources = new List<IDisposable> {
|
||||
ObservableAutoWrapper.connect<SortDescriptionCollection, SortDescription, string>(
|
||||
rows_view.SortDescriptions, live_sorting_props = ((ICollectionViewLiveShaping)rows_view).LiveSortingProperties, sd => sd.PropertyName),
|
||||
(launch_settings_editor = new DialogCommand(create_settings_view))
|
||||
};
|
||||
//
|
||||
PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
pa.set_predicate_animations(_animation_defs);
|
||||
return pa;
|
||||
}
|
||||
//PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
// var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
// pa.set_predicate_animations(_animation_defs);
|
||||
// return pa;
|
||||
//}
|
||||
Window create_settings_view() {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateBuilder>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm);
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm, _create_settings_obj);
|
||||
// need to refresh before launching the settings-view bc users can chanve some grid-column-settings properties directly from the grid (e.g. width, is_collapsed, etc.)
|
||||
_grid_settings.set(grid_settings.with(columns.Select(c => c.create())));
|
||||
vm.load_from((grid_settings, _predicate_animations_data));
|
||||
vm.load(_create_settings_obj(grid_settings, _predicate_animations_data));
|
||||
if (column_flags != null) {
|
||||
foreach (var col in vm.columns) {
|
||||
if (column_flags.TryGetValue(col.id, out GridColumnSettingsFeatureFlags f))
|
||||
@@ -84,10 +95,10 @@ namespace mbts.ui {
|
||||
}
|
||||
var window = new GridSettingsDialog { DataContext = vm };
|
||||
vm.committed += () => {
|
||||
var (grid, predicate_animations_data) = vm.create();
|
||||
TSettingsData sd = vm.get_result();
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
grid = grid.with(sorting_criterion: rows_view.get_sorting_criteria(columns));
|
||||
load_from((grid, predicate_animations_data));
|
||||
sd = _create_settings_obj(sd.general.with(sorting_criterion: rows_view.get_sorting_criteria(columns)), sd.predicate_animations);
|
||||
load_from(sd);
|
||||
};
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(window);
|
||||
return window;
|
||||
@@ -107,20 +118,37 @@ namespace mbts.ui {
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
public IDialogCommand launch_settings_editor { get; }
|
||||
|
||||
public (GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) create() {
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
_grid_settings.set(grid_settings.with(_columns.Select(c => c.create()), _rows_view.get_sorting_criteria(_columns)));
|
||||
return (grid_settings, _predicate_animations_data);
|
||||
public GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> create_vm(GridSettings settings, IObservable<string> title) {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData>(title, _bus, _orders_model, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load(_create_settings_obj(settings, _predicate_animations_data));
|
||||
return vm;
|
||||
}
|
||||
|
||||
public void load_from((GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) value) {
|
||||
public TSettingsData create() {
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
_grid_settings.set(grid_settings.with(_columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
|
||||
col_settings = store.apply(col_settings);
|
||||
return col_settings;
|
||||
}), _rows_view.get_sorting_criteria(_columns)));
|
||||
return _create_settings_obj(grid_settings, _predicate_animations_data);
|
||||
}
|
||||
|
||||
public void load_from(TSettingsData value) => _load_from_core(value, true);
|
||||
|
||||
void _load_from_core(TSettingsData value, bool reset_immutables) {
|
||||
Disposable.dispose(_notification_subs);
|
||||
var grid = value.grid ?? GridSettings.Default;
|
||||
var grid = value.general ?? GridSettings.Default;
|
||||
_grid_settings.set(grid);
|
||||
foreach (ColumnBase col in _columns)
|
||||
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
|
||||
_rows_view.set_sort_descriptions(grid.sorting_criterion, _columns);
|
||||
_predicate_animations_data = value.predicate_animations_data;
|
||||
if (reset_immutables) {
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_rows_view.set_sort_descriptions(grid.sorting_criterion, _columns);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
_predicate_animations_data = value.predicate_animations;
|
||||
_animation_defs.clear();
|
||||
foreach (var pa in _predicate_animations_data) {
|
||||
var predicate = pa.predicate_builder.create_predicate(_arg);
|
||||
@@ -134,12 +162,18 @@ namespace mbts.ui {
|
||||
_animation_defs.add(pa.predicate_builder.create_predicate(_arg), PredicateAnimations.flat_definitions<TRow, TRowData>(pa.row_animations, pa.cell_animations, lookup_column));
|
||||
}
|
||||
}
|
||||
foreach (var a in _animators) {
|
||||
a.set_predicate_animations(_animation_defs);
|
||||
}
|
||||
//foreach (var a in _animators) {
|
||||
// a.set_predicate_animations(_animation_defs);
|
||||
//}
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null && _columns != null) {
|
||||
if (reset_immutables) {
|
||||
foreach (var kv in _column_flags) {
|
||||
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
|
||||
kv.Value.store(cs);
|
||||
}
|
||||
}
|
||||
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
|
||||
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
|
||||
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
|
||||
@@ -159,5 +193,26 @@ namespace mbts.ui {
|
||||
IGridColumn<TRow> lookup_column(string col_id) =>
|
||||
_columns.OfType<IGridColumn<TRow>>().FirstOrDefault(c => string.Equals(col_id, c.id, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
void IObserver<TSettingsData>.OnNext(TSettingsData sd) {
|
||||
if (sd == null) return;
|
||||
try {
|
||||
var grid = sd.general;
|
||||
if (grid.columns != null) {
|
||||
var cols_with_applied_flags = grid.columns.ToList();
|
||||
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
|
||||
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
|
||||
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
|
||||
}
|
||||
}
|
||||
sd = _create_settings_obj(grid.with(cols_with_applied_flags), sd.predicate_animations);
|
||||
}
|
||||
_load_from_core(sd, false);
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error loading GridSettings (as observer): {ex}");
|
||||
}
|
||||
}
|
||||
void IObserver<TSettingsData>.OnError(Exception error) { }
|
||||
void IObserver<TSettingsData>.OnCompleted() { }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui;
|
||||
@@ -18,14 +19,38 @@ using CellAnimation = mbts.ui.CellAnimation;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IGridSettingsFactory<TRowData, TPredicateBuilder> : IValueFactory<(GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data)>
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData>, new() { }
|
||||
interface IGridSettingsWithNotifications<TRowData, TPredicateBuilder> where TPredicateBuilder : IPredicateBuilder<TRowData> {
|
||||
GridSettings general { get; }
|
||||
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations { get; }
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateBuilder> : DisposableViewModelBase, IGridSettingsFactory<TRowData, TPredicateBuilder>, IRequestViewClose
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData>, new() {
|
||||
class GridSettingsWithNotifications<TRowData, TPredicateBuilder> : IEquatable<GridSettingsWithNotifications<TRowData, TPredicateBuilder>> where TPredicateBuilder : IPredicateBuilder<TRowData> {
|
||||
public readonly GridSettings grid;
|
||||
public readonly IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data;
|
||||
public GridSettingsWithNotifications(GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) {
|
||||
this.grid = grid;
|
||||
this.predicate_animations_data = predicate_animations_data;
|
||||
}
|
||||
|
||||
public bool Equals(GridSettingsWithNotifications<TRowData, TPredicateBuilder> other) =>
|
||||
other != null &&
|
||||
EqualityComparer.null_check_equals(grid, other.grid) &&
|
||||
EqualityComparer.safe_sequence_equals(predicate_animations_data, other.predicate_animations_data);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as GridSettingsWithNotifications<TRowData, TPredicateBuilder>);
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
EqualityComparer.safe_get_hash_code(grid),
|
||||
EqualityComparer.safe_aggregate_hash(predicate_animations_data));
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<TSettingsData>, ITemplateSelectorKey
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData>, new()
|
||||
where TSettingsData :class, IGridSettingsWithNotifications<TRowData, TPredicateBuilder> {
|
||||
//
|
||||
protected readonly Func<IPropertyWrapperVM<TPredicateBuilder>> _create_predicate_vm;
|
||||
protected readonly Func<string, IPropertyWrapperVM<TPredicateBuilder>> _create_column_predicate_vm;
|
||||
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> _create_settings_obj;
|
||||
readonly Property<IColumnVM> _current_column;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
@@ -33,16 +58,22 @@ namespace mbts.spark {
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
|
||||
|
||||
public GridSettingsVM(
|
||||
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
|
||||
Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm, Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm) {
|
||||
Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm,
|
||||
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm,
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
//
|
||||
_owner_title = backing(nameof(owner_title), owner_title);
|
||||
this.bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_create_predicate_vm = create_predicate_vm;
|
||||
_create_column_predicate_vm = create_column_predicate_vm;
|
||||
_create_settings_obj = create_settings_obj;
|
||||
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
|
||||
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
|
||||
_font_size = backing(nameof(font_size), FontSizes.Small);
|
||||
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
|
||||
@@ -68,6 +99,7 @@ namespace mbts.spark {
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
|
||||
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
|
||||
public bool enable_sound_triggers => true;
|
||||
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
|
||||
public bool show_cell_predicate_animations { get => _show_cell_predicate_animations.Value; set => _show_cell_predicate_animations.set(value); }
|
||||
public ObservableEditableCollection<PredicateRowAnimationVM> predicate_animations { get; }
|
||||
@@ -80,7 +112,9 @@ namespace mbts.spark {
|
||||
new DialogSkirtItem("Apply", new DelegateCommand(() => committed?.Invoke()))
|
||||
};
|
||||
|
||||
public (GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) create() {
|
||||
public string key => ui.spark.GridOrLadderSettingsSelectorKeys.SimpleGrid;
|
||||
|
||||
public TSettingsData get_result() {
|
||||
var animation_map = predicate_animations
|
||||
.Select(p => p.create())
|
||||
.GroupBy(d => d.predicate)
|
||||
@@ -94,22 +128,22 @@ namespace mbts.spark {
|
||||
foreach (IGrouping<TPredicateBuilder, CellAnimation> g in animations)
|
||||
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
|
||||
}
|
||||
return (new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), 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),
|
||||
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateBuilder>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
|
||||
//
|
||||
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateBuilder, CellAnimation> c = null) =>
|
||||
(r?.ToList() ?? new List<RowAnimation>(), s?.ToList() ?? new List<SoundNotification>(), c?.ToList() ?? new List<CellAnimation>());
|
||||
}
|
||||
|
||||
public void load_from((GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) value) {
|
||||
_font_family.set(new FontFamily(value.grid.font_family));
|
||||
_font_weight.set(value.grid.font_weight);
|
||||
font_color.load_from(value.grid.font_color.rgb_solid_brush());
|
||||
selection_highlight.load_from(value.grid.selection_highlight.to_brush());
|
||||
_font_size.set(value.grid.font_size);
|
||||
public void load(TSettingsData sd) {
|
||||
_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.load_from(sd.general.selection_highlight.to_brush());
|
||||
_font_size.set(sd.general.font_size);
|
||||
var col_animations = new Dictionary<string, Dictionary<TPredicateBuilder, List<CellAnimation>>>();
|
||||
predicate_animations.Clear();
|
||||
foreach (var pa in value.predicate_animations_data) {
|
||||
foreach (var pa in sd.predicate_animations) {
|
||||
if (pa.row_animations.Count > 0 || pa.sound_notifications.Count > 0) {
|
||||
var vm = new PredicateRowAnimationVM(_create_predicate_vm());
|
||||
vm.load_from((pa.predicate_builder, pa.row_animations, pa.sound_notifications));
|
||||
@@ -122,9 +156,14 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
var curr_col_id = current_column?.id;
|
||||
columns.SetItems(value.grid.columns.Select(col => {
|
||||
columns.SetItems(sd.general.columns.Select(col => {
|
||||
var vm = new ColumnVM(this);
|
||||
vm.load_from((col, col_animations.GetValueOrDefault(col.id)?.ToFlatLookup(kv => kv.Key, kv => kv.Value)));
|
||||
// reapply our favorite column flags
|
||||
// this comes in handy when performing Load From Factory - the editor will contain
|
||||
// a consistent view of what can be overridden and what cannot
|
||||
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
|
||||
vm.apply_flags(flags);
|
||||
return vm;
|
||||
}));
|
||||
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out IColumnVM curr_column))
|
||||
|
||||
@@ -342,9 +342,8 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSplit}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!--TODO: enable when FontFamilies are ready-->
|
||||
<TextBlock Foreground="{u:BTSColor disabled_text}">Font</TextBlock>
|
||||
<Grid Grid.Column="2" IsEnabled="False" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}">
|
||||
<TextBlock>Font</TextBlock>
|
||||
<Grid Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}">
|
||||
<c:PopupControl HorizontalAlignment="Stretch" Header="{Binding}" HeaderTemplate="{StaticResource FontPickerPopupHeaderTemplate}" Background="{StaticResource ComboBox.Static.Background}">
|
||||
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
|
||||
</c:PopupControl>
|
||||
@@ -359,6 +358,9 @@
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource {x:Type GroupBox}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding enable_sound_triggers}" Value="{u:False}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding show_predicate_animations}" Value="{u:False}">
|
||||
<Setter Property="Height" Value="{StaticResource CollapsedAnimationItemsHeight}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace mbts.ui.spark {
|
||||
|
||||
public static IMultiValueConverter ColumnDisplayNameVisibilityConverter =>
|
||||
ValueConverter.create<string, string, Visibility>((id, header) => id == header ? Visibility.Collapsed : Visibility.Visible);
|
||||
|
||||
public bool EnableSoundTriggers { get; set; }
|
||||
}
|
||||
|
||||
static class PredicateTemplateKeys {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using bts.proto;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.ui {
|
||||
abstract class ImmutableColumnSettingStore {
|
||||
public readonly GridColumnSettingsFeatureFlags flags;
|
||||
|
||||
protected ImmutableColumnSettingStore(GridColumnSettingsFeatureFlags flags) =>
|
||||
this.flags = flags;
|
||||
|
||||
public static ImmutableColumnSettingStore create(GridColumnSettingsFeatureFlags flags) {
|
||||
var stores_per_flag = new List<ImmutableColumnSettingStore>();
|
||||
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) != 0) {
|
||||
stores_per_flag.Add(new ImmutableFontColorOverrideStore());
|
||||
}
|
||||
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) != 0) {
|
||||
stores_per_flag.Add(new ImmutableFontWeightOverrideStore());
|
||||
}
|
||||
return new CompositeImmutableSettingsStore(stores_per_flag, flags);
|
||||
}
|
||||
|
||||
public abstract GridColumnSettings apply(GridColumnSettings settings);
|
||||
public abstract void store(GridColumnSettings settings);
|
||||
|
||||
class ImmutableFontColorOverrideStore : ImmutableColumnSettingStore {
|
||||
NullableColorProto _value;
|
||||
|
||||
public ImmutableFontColorOverrideStore()
|
||||
: base(GridColumnSettingsFeatureFlags.ImmutableFontColor) { }
|
||||
|
||||
public override GridColumnSettings apply(GridColumnSettings settings) =>
|
||||
settings.with(font_color_override: _value);
|
||||
|
||||
public override void store(GridColumnSettings settings) =>
|
||||
_value = new NullableColorProto(settings.font_color_override);
|
||||
}
|
||||
|
||||
class ImmutableFontWeightOverrideStore : ImmutableColumnSettingStore {
|
||||
NullableFontWeightProto _value;
|
||||
|
||||
public ImmutableFontWeightOverrideStore()
|
||||
: base(GridColumnSettingsFeatureFlags.ImmutableFontWeight) { }
|
||||
|
||||
public override GridColumnSettings apply(GridColumnSettings settings) =>
|
||||
settings.with(font_weight_override: _value);
|
||||
|
||||
public override void store(GridColumnSettings settings) =>
|
||||
_value = new NullableFontWeightProto(settings.font_weight_override);
|
||||
}
|
||||
|
||||
class CompositeImmutableSettingsStore : ImmutableColumnSettingStore {
|
||||
readonly IReadOnlyList<ImmutableColumnSettingStore> _stores;
|
||||
public CompositeImmutableSettingsStore(IReadOnlyList<ImmutableColumnSettingStore> stores, GridColumnSettingsFeatureFlags flags)
|
||||
: base(flags) => _stores = stores;
|
||||
|
||||
public override GridColumnSettings apply(GridColumnSettings settings) {
|
||||
foreach (var s in _stores) {
|
||||
settings = s.apply(settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
public override void store(GridColumnSettings settings) {
|
||||
foreach (var s in _stores) {
|
||||
s.store(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using bts.proto;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SimpleGridSettingsVM {
|
||||
public class SimpleColumnVM : ViewModelBase, ISimpleColumnVM {
|
||||
public readonly SimpleGridSettingsVM owner;
|
||||
|
||||
readonly Property<string> _id, _display_name;
|
||||
readonly Property<double> _width;
|
||||
readonly Property<HorizontalAlignment> _horizontal_alignment;
|
||||
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
|
||||
readonly Property<FontWeight> _font_weight_override;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public SimpleColumnVM(SimpleGridSettingsVM owner) {
|
||||
this.owner = owner;
|
||||
_id = backing(nameof(id), string.Empty);
|
||||
_display_name = backing(nameof(display_name), string.Empty);
|
||||
_width = backing(nameof(width), 70.0);
|
||||
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
|
||||
_is_visible = backing(nameof(is_visible), false);
|
||||
// font color override
|
||||
_can_override_font_color = backing(nameof(can_override_font_color), true);
|
||||
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
|
||||
// font weight override
|
||||
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
|
||||
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
|
||||
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
|
||||
}
|
||||
|
||||
public string id => _id.Value;
|
||||
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
|
||||
public double width { get => _width.Value; set => _width.set(value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
|
||||
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
|
||||
// font color override
|
||||
public bool can_override_font_color => _can_override_font_color.Value;
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight override
|
||||
public bool can_override_font_weight => _can_override_font_weight.Value;
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
|
||||
|
||||
public GridColumnSettings create() =>
|
||||
_settings.with(display_name, is_visible, null,
|
||||
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : (NullableFontWeightProto?)null,
|
||||
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : (NullableColorProto?)null,
|
||||
width,
|
||||
horizontal_alignment);
|
||||
|
||||
public void load_from(GridColumnSettings value) {
|
||||
_settings = value;
|
||||
_id.set(_settings.id);
|
||||
_display_name.set(_settings.display_name);
|
||||
_width.set(_settings.width);
|
||||
_horizontal_alignment.set(_settings.horizontal_alignment);
|
||||
_is_visible.set(_settings.is_visible);
|
||||
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
|
||||
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override.set(_settings.font_weight_override.Value);
|
||||
}
|
||||
if (is_font_color_overridden) {
|
||||
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
|
||||
var split_flags = EnumHelper.split_flags(flags).ToList();
|
||||
// font color
|
||||
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
|
||||
if (!can_override_font_color)
|
||||
_is_font_color_overridden.set(false);
|
||||
// font weight
|
||||
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
|
||||
if (!can_override_font_weight)
|
||||
_is_font_weight_overridden.set(false);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{display_name} ({id})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.SimpleGridSettingsDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Style="{StaticResource BTSDialog}"
|
||||
>
|
||||
<Window.Resources>
|
||||
<cv:BooleanToVisibilityConverter x:Key="DefaultTabVisibilityConverter" VisibilityForTrue="Visible" VisibilityForFalse="Hidden"/>
|
||||
<cv:BooleanToVisibilityConverter x:Key="CustomTabVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="TabHeaderFontWeightConverter" Default="{u:FontWeight Normal}">
|
||||
<cv:KeyValue Key="{u:True}" Value="{u:FontWeight Bold}"/>
|
||||
</cv:ValueMap>
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource ComboBox.Static.Border}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
|
||||
<Setter Property="Padding" Value="6,3"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Padding" Value="6,4,6,6"/>
|
||||
<Setter Property="BorderThickness" Value="1,1,1,0"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="False"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.Button.mouseover_background}"/>
|
||||
</MultiTrigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.Button.disabled_foreground}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<u:KeyedStyleSelector x:Key="LeftButtonStyleSelector">
|
||||
<u:StyleSelectorKeyValue Key="{x:Static usp:SimpleGridSettingsDialog.ResetToFactoryKey}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
|
||||
<Setter Property="Width" Value="94"/>
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
<u:StyleSelectorKeyValue Key="{x:Static usp:SimpleGridSettingsDialog.CopyFromOtherKey}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
|
||||
<Setter Property="Width" Value="108"/>
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
</u:KeyedStyleSelector>
|
||||
<u:KeyedTemplateSelector x:Key="GridOrLadderSettingsSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridOrLadderSettingsSelectorKeys.SimpleGrid}">
|
||||
<DataTemplate>
|
||||
<usp:GridSettingsView />
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridOrLadderSettingsSelectorKeys.Ladder}">
|
||||
<DataTemplate>
|
||||
<usp:LadderSettingsView />
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:ActionMenu HorizontalAlignment="Right" ItemsSource="{Binding action_menu_items}"/>
|
||||
<ContentControl Grid.Row="2"
|
||||
Margin="8"
|
||||
VerticalAlignment="Top"
|
||||
Content="{Binding instance_vm}"
|
||||
ContentTemplateSelector="{StaticResource GridOrLadderSettingsSelector}"
|
||||
/>
|
||||
<c:DialogSkirt
|
||||
Grid.Row="3"
|
||||
BorderThickness="1"
|
||||
BorderBrush="DarkGray"
|
||||
LeftItems="{Binding left_skirt_items}"
|
||||
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
|
||||
RightItems="{Binding right_skirt_items}"
|
||||
>
|
||||
<c:DialogSkirt.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Color="Transparent"/>
|
||||
<GradientStop Color="{x:Static u:BTSColors.dialog_skirt}" Offset="1"/>
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</c:DialogSkirt.Background>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for SimpleGridSettingsDialog.xaml
|
||||
/// </summary>
|
||||
public partial class SimpleGridSettingsDialog : Window {
|
||||
public SimpleGridSettingsDialog() =>
|
||||
InitializeComponent();
|
||||
|
||||
public const string ResetToFactoryKey = "Reset", CopyFromOtherKey = "Copy";
|
||||
}
|
||||
|
||||
static class GridOrLadderSettingsSelectorKeys {
|
||||
public const string
|
||||
SimpleGrid = nameof(SimpleGrid),
|
||||
Ladder = nameof(Ladder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.ui {
|
||||
class SimpleGridSettingsManager<TRow> : ViewModelBase, IObserver<GridSettings> {
|
||||
readonly ObservableCollection<ColumnBase> _columns;
|
||||
readonly IReadOnlyCollection<IRowVM> _row_vms;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
readonly DelegateProperty<double> _font_size;
|
||||
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight;
|
||||
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
|
||||
|
||||
public SimpleGridSettingsManager(ObservableCollection<ColumnBase> columns)
|
||||
: this(columns, new Dictionary<string, GridColumnSettingsFeatureFlags>(), null) { }
|
||||
|
||||
public SimpleGridSettingsManager(ObservableCollection<ColumnBase> columns, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags, IReadOnlyCollection<IRowVM> row_vms) {
|
||||
_columns = columns;
|
||||
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
|
||||
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
|
||||
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
|
||||
_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.to_brush().frozen(), nameof(grid_settings));
|
||||
_row_vms = row_vms;
|
||||
}
|
||||
|
||||
public GridSettings grid_settings => _grid_settings.Value;
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
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 SimpleGridSettingsVM create_vm(GridSettings settings, IObservable<string> title) {
|
||||
var vm = new SimpleGridSettingsVM(title, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load_from(settings);
|
||||
return vm;
|
||||
}
|
||||
|
||||
public GridSettings create() {
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
_grid_settings.set(grid_settings.with(_columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
|
||||
col_settings = store.apply(col_settings);
|
||||
return col_settings;
|
||||
})));
|
||||
return grid_settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will overwrite the columns' immutable-value store (unlike the IObserver<<see cref="GridSettings"/>> 'OnNext' implementation)
|
||||
/// </summary>
|
||||
public void load_from(GridSettings grid) => _load_from_core(grid, true);
|
||||
|
||||
void IObserver<GridSettings>.OnCompleted() { }
|
||||
void IObserver<GridSettings>.OnError(Exception error) { }
|
||||
void IObserver<GridSettings>.OnNext(GridSettings grid) {
|
||||
if (grid.columns != null) {
|
||||
var cols_with_applied_flags = grid.columns.ToList();
|
||||
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
|
||||
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
|
||||
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
|
||||
}
|
||||
}
|
||||
grid = grid.with(cols_with_applied_flags);
|
||||
}
|
||||
_load_from_core(grid, false);
|
||||
}
|
||||
|
||||
void _load_from_core(GridSettings grid, bool reset_immutable_flag_stores) {
|
||||
grid = grid ?? GridSettings.Default;
|
||||
_grid_settings.set(grid);
|
||||
foreach (ColumnBase col in _columns)
|
||||
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null) {
|
||||
if (reset_immutable_flag_stores) {
|
||||
foreach (var kv in _column_flags) {
|
||||
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
|
||||
kv.Value.store(cs);
|
||||
}
|
||||
}
|
||||
if (_columns != null) {
|
||||
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
|
||||
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
|
||||
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
|
||||
CollectionHelper.sync_with_never_duplicate(_columns, grid_settings.columns, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => id_key(c.id), cs => id_key(cs.id));
|
||||
}
|
||||
// ** don't assume that columns:column_settings have a perfect 1:1 relationship (they should, but it is possible we accidentally break the settings as grids/uis change) **
|
||||
foreach (var col in _columns) {
|
||||
if (col_settings_id_map.TryGetValue(id_key(col.id), out GridColumnSettings cs))
|
||||
col.load_from(cs);
|
||||
}
|
||||
}
|
||||
// invalidate all the rows to draw them again to reflect the latest settings
|
||||
if (_row_vms != null)
|
||||
foreach (var r in _row_vms) r.raise_invalidated();
|
||||
|
||||
//
|
||||
string id_key(string id) => id.ToLower();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 445 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user