Compare commits
1804 Commits
1.77.2
...
renovate/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93f6d017e6 | ||
|
|
455d897b9f | ||
|
|
3d4d1136f0 | ||
|
|
598136f089 | ||
|
|
865bddfb04 | ||
|
|
3b2bc028cc | ||
|
|
71c746f801 | ||
|
|
079814751e | ||
|
|
1de9ce6ef5 | ||
|
|
01df0e295e | ||
|
|
2631926eda | ||
|
|
db1496d4a2 | ||
|
|
a8b12c9ba1 | ||
|
|
a2b408e537 | ||
|
|
a871624ab7 | ||
|
|
e40a800bc4 | ||
|
|
48cb1ce770 | ||
|
|
6733d758aa | ||
|
|
f78b9bf51c | ||
|
|
213ea6a4d3 | ||
|
|
ac3d7e6366 | ||
|
|
9fae2e066e | ||
|
|
cf66e84d31 | ||
|
|
f7326aff62 | ||
|
|
d4bca6b03d | ||
|
|
1d86015f9d | ||
|
|
5efcc653eb | ||
|
|
333588e101 | ||
|
|
4046681fc5 | ||
|
|
91c7df22b2 | ||
|
|
173b208eb1 | ||
|
|
50de37c3a4 | ||
|
|
380e91de07 | ||
|
|
ba97933f33 | ||
|
|
539b761199 | ||
|
|
aaff6e4548 | ||
|
|
a1e3b34580 | ||
|
|
276585e379 | ||
|
|
9242dc0faf | ||
|
|
14d08d8d62 | ||
|
|
421d8eb581 | ||
|
|
aed0006f1d | ||
|
|
86d986a633 | ||
|
|
5163aeb4d2 | ||
|
|
a103939c64 | ||
|
|
bcb8e05698 | ||
|
|
2e74313f07 | ||
|
|
c9791454ec | ||
|
|
b1c1696acb | ||
|
|
ab668ac677 | ||
|
|
1777c4840a | ||
|
|
90fcd672d8 | ||
|
|
4226396cbf | ||
|
|
3591ca0f4c | ||
|
|
09cbcccf30 | ||
|
|
4e36b5666e | ||
|
|
308253a325 | ||
|
|
30bb4016b4 | ||
|
|
e616ae16e1 | ||
|
|
469528a07a | ||
|
|
c7f831e9f9 | ||
|
|
dd9922be45 | ||
|
|
d0a468c22b | ||
|
|
ff5dbc88fe | ||
|
|
3a4ae9b098 | ||
|
|
2de24e534c | ||
|
|
59412a65e1 | ||
|
|
adedb6962f | ||
|
|
d3fa608ae9 | ||
|
|
3159903875 | ||
|
|
31c28c4917 | ||
|
|
2c13f7c3a7 | ||
|
|
725ee92147 | ||
|
|
14406f79a2 | ||
|
|
3e202c3a19 | ||
|
|
412c727e4c | ||
|
|
31e7b9e443 | ||
|
|
a18e292765 | ||
|
|
258ea87f90 | ||
|
|
fd9eabe1e6 | ||
|
|
a9dd06df45 | ||
|
|
dfc24b0cb2 | ||
|
|
63f5325f29 | ||
|
|
6f7949214b | ||
|
|
6cfec060a0 | ||
|
|
db733424ca | ||
|
|
49eab4d377 | ||
|
|
53e5396031 | ||
|
|
99d01130bf | ||
|
|
62ce6cd6e7 | ||
|
|
35c66b0e4a | ||
|
|
dafc05dc42 | ||
|
|
458a05ea5f | ||
|
|
38acc1e960 | ||
|
|
f83209a2b9 | ||
|
|
9d1546c8b7 | ||
|
|
dca2517cf0 | ||
|
|
ff54ca9015 | ||
|
|
76cb0a1e0b | ||
|
|
c683854678 | ||
|
|
e9d0a8aa69 | ||
|
|
30cb1de711 | ||
|
|
699b93e175 | ||
|
|
c7df6f3715 | ||
|
|
f1d1a19779 | ||
|
|
1c2b5fadc4 | ||
|
|
97f5daa13d | ||
|
|
63e100c233 | ||
|
|
92a710e72a | ||
|
|
fe82146f47 | ||
|
|
64b0496ef7 | ||
|
|
7d21b3de9e | ||
|
|
68a8caea34 | ||
|
|
aee18bae3b | ||
|
|
ff58cae677 | ||
|
|
548f938757 | ||
|
|
b9c2e82af1 | ||
|
|
23bb5ec7be | ||
|
|
0b7b87d84a | ||
|
|
b84a49e99c | ||
|
|
327cc46c8d | ||
|
|
8540434594 | ||
|
|
41536ba971 | ||
|
|
8ddaf024ff | ||
|
|
7e8f647f54 | ||
|
|
9a32a7c68c | ||
|
|
280a930792 | ||
|
|
2540d39dd8 | ||
|
|
2b00fa0ad9 | ||
|
|
810aad90ed | ||
|
|
59c8968d8a | ||
|
|
4bfed87d81 | ||
|
|
5c36477b1f | ||
|
|
a529b978e1 | ||
|
|
eae83eadab | ||
|
|
297260af9a | ||
|
|
f5d38c282f | ||
|
|
493e5eda6a | ||
|
|
88b8201384 | ||
|
|
b88204c261 | ||
|
|
d39fb10b47 | ||
|
|
96e7313b69 | ||
|
|
02be546c5b | ||
|
|
c5b98c6cc8 | ||
|
|
687a4088ab | ||
|
|
8c63843c22 | ||
|
|
469ca48592 | ||
|
|
208ce663b2 | ||
|
|
843243c75e | ||
|
|
b7ed5a300d | ||
|
|
415a649a76 | ||
|
|
462d9c39af | ||
|
|
d52d79dd69 | ||
|
|
5dbc6e74e0 | ||
|
|
986b0084bf | ||
|
|
78bdf02c14 | ||
|
|
b4ea8f00ad | ||
|
|
7fd992e9f1 | ||
|
|
76e739cdc1 | ||
|
|
0e0cc1c884 | ||
|
|
21fba7f371 | ||
|
|
3f3a0c3c21 | ||
|
|
1db87b18de | ||
|
|
44b01bca96 | ||
|
|
441b13bed6 | ||
|
|
dd72e9edb7 | ||
|
|
8bbcc9b877 | ||
|
|
98580a8dc6 | ||
|
|
3600905a39 | ||
|
|
c207685b91 | ||
|
|
683f3bcb80 | ||
|
|
23f964ba28 | ||
|
|
56271ba346 | ||
|
|
252a402f4f | ||
|
|
d2d0722ac2 | ||
|
|
ce9bdccc4d | ||
|
|
2a992aa1d8 | ||
|
|
f73729bbe8 | ||
|
|
8634edbd8c | ||
|
|
cc3ead0799 | ||
|
|
566cd26e73 | ||
|
|
2354d5a8a4 | ||
|
|
778067a590 | ||
|
|
d8d9c844d1 | ||
|
|
dc6655514a | ||
|
|
feb3b72fd0 | ||
|
|
479e18b204 | ||
|
|
b1c169d0b8 | ||
|
|
7f75fadb31 | ||
|
|
6af70543ea | ||
|
|
59d5d575ad | ||
|
|
05f2b84cf5 | ||
|
|
a39d4a3063 | ||
|
|
1da019575e | ||
|
|
195c4be4e2 | ||
|
|
4dd672e600 | ||
|
|
f3a14c5ede | ||
|
|
af696a96aa | ||
|
|
d7de52fb4c | ||
|
|
ae66a8fcfc | ||
|
|
56d18774a8 | ||
|
|
7ba22c5555 | ||
|
|
dab7715eb7 | ||
|
|
ec8bac933d | ||
|
|
958ec65b17 | ||
|
|
e775acc209 | ||
|
|
d23631faef | ||
|
|
518401b2c4 | ||
|
|
20c3ebd662 | ||
|
|
8d0d03e152 | ||
|
|
632cfca71f | ||
|
|
221ff38a66 | ||
|
|
3ac7365c9b | ||
|
|
be53755010 | ||
|
|
30eafa3efa | ||
|
|
7c0a6a51d4 | ||
|
|
181f96823e | ||
|
|
7b4e9781ac | ||
|
|
7a4ff98ba8 | ||
|
|
100c2e3078 | ||
|
|
cc6f07d943 | ||
|
|
cc3a9c3a00 | ||
|
|
ce1d82c730 | ||
|
|
ec3a01de70 | ||
|
|
af894964fc | ||
|
|
5cebc4d418 | ||
|
|
04e0144004 | ||
|
|
c33c9814c2 | ||
|
|
3a946e5810 | ||
|
|
6a6a894a1c | ||
|
|
e1ffb9262d | ||
|
|
b3eb6904f9 | ||
|
|
72cbade402 | ||
|
|
6fd008c2e8 | ||
|
|
25f26f08de | ||
|
|
7ebc62d8e3 | ||
|
|
e04ba4f9e0 | ||
|
|
0d4324b009 | ||
|
|
6404956a62 | ||
|
|
769db78ee8 | ||
|
|
8bb8d52b1d | ||
|
|
e990659d05 | ||
|
|
0fcef353ae | ||
|
|
bf91a8a709 | ||
|
|
d81a608b3a | ||
|
|
8413ee77d4 | ||
|
|
4d92a30089 | ||
|
|
57f7e09bb6 | ||
|
|
19fdccc540 | ||
|
|
83c2f2a7a8 | ||
|
|
9dcea7ed6e | ||
|
|
6ad3de91e9 | ||
|
|
2bbbd166f9 | ||
|
|
327ba3f4e1 | ||
|
|
c9b77b6616 | ||
|
|
a94e58c83e | ||
|
|
d40794d7a0 | ||
|
|
7dd916dd80 | ||
|
|
d16d76d0fa | ||
|
|
a896949752 | ||
|
|
f63186a7a6 | ||
|
|
ee88e835ed | ||
|
|
1288a274f3 | ||
|
|
facc523aae | ||
|
|
11dfdd797d | ||
|
|
3dc37d0359 | ||
|
|
b00185e1dc | ||
|
|
44c65775ad | ||
|
|
9c31ee28b6 | ||
|
|
5868f91d2d | ||
|
|
ee3735904d | ||
|
|
2b39dde523 | ||
|
|
611fdcebe9 | ||
|
|
b6c21bb21f | ||
|
|
ed2fee3195 | ||
|
|
2d7a897a4d | ||
|
|
4f14c78b58 | ||
|
|
8b8c1d919e | ||
|
|
e960353de6 | ||
|
|
e563a4c319 | ||
|
|
131aef7069 | ||
|
|
73c3e078d1 | ||
|
|
8858d55970 | ||
|
|
f983f5de02 | ||
|
|
a8efba1ed4 | ||
|
|
8fc3682cc9 | ||
|
|
4ade871aba | ||
|
|
0a381e0a44 | ||
|
|
895e16d4cb | ||
|
|
6657fa3ad0 | ||
|
|
f5fcc7c206 | ||
|
|
435e6f46c1 | ||
|
|
9ef81e6e72 | ||
|
|
e4b4400aa5 | ||
|
|
1d9e082a4a | ||
|
|
43d11a5de2 | ||
|
|
94db6caa2c | ||
|
|
11366e5d8d | ||
|
|
a2967fa999 | ||
|
|
12920bc6c6 | ||
|
|
55fec45b58 | ||
|
|
045aa631e4 | ||
|
|
3cf3a70fc7 | ||
|
|
290dab29f1 | ||
|
|
79b252f839 | ||
|
|
49bd76f197 | ||
|
|
54492bedfb | ||
|
|
2d1c2df2cb | ||
|
|
1187e1c95e | ||
|
|
664a63211e | ||
|
|
b7df1e80c8 | ||
|
|
2d6563022e | ||
|
|
5423c84eed | ||
|
|
d263fb1b80 | ||
|
|
c72bd2a107 | ||
|
|
8e4627c872 | ||
|
|
ccbe99f828 | ||
|
|
ee483e3b2b | ||
|
|
5b4b13828b | ||
|
|
4b0bc71fb0 | ||
|
|
c28ae335b2 | ||
|
|
2d3830b747 | ||
|
|
fe4e205772 | ||
|
|
5e2fc8b0dc | ||
|
|
6403573e36 | ||
|
|
39b863b57e | ||
|
|
8e5cdcb6e5 | ||
|
|
f5b7cd6fb1 | ||
|
|
493ddc1f1f | ||
|
|
5ad338bdcc | ||
|
|
d1a1adb994 | ||
|
|
c879b030ad | ||
|
|
8d1d55c38e | ||
|
|
aef576d290 | ||
|
|
e34b0fded3 | ||
|
|
14e5e6a715 | ||
|
|
0579273964 | ||
|
|
5839a836c8 | ||
|
|
236fff8014 | ||
|
|
c1efefae81 | ||
|
|
1e5bd44332 | ||
|
|
7478537f3e | ||
|
|
b0f50b825f | ||
|
|
7509fcda52 | ||
|
|
0991cf74ef | ||
|
|
61dd2ec8db | ||
|
|
77643848b6 | ||
|
|
21ab172041 | ||
|
|
d6c2c2158a | ||
|
|
0c50e2d78e | ||
|
|
64183e8e7d | ||
|
|
b9595d1650 | ||
|
|
fa3be420b8 | ||
|
|
e2d1929553 | ||
|
|
4f05e8dbe9 | ||
|
|
ea9e79d930 | ||
|
|
e1a8d60cd7 | ||
|
|
4e20049ea3 | ||
|
|
89ccd33fd1 | ||
|
|
4668104e36 | ||
|
|
b4aa30de23 | ||
|
|
526cb1fb47 | ||
|
|
03b395ee15 | ||
|
|
4125344254 | ||
|
|
531e117731 | ||
|
|
588fdca4d7 | ||
|
|
285ca46c18 | ||
|
|
8bdb83eb73 | ||
|
|
d38b71d5ea | ||
|
|
687f06937d | ||
|
|
e0799a6772 | ||
|
|
4943dce1a8 | ||
|
|
8dee5c0d3c | ||
|
|
018b07299e | ||
|
|
6f52b82a6d | ||
|
|
e1ee905286 | ||
|
|
6853b158ce | ||
|
|
d705b8a45d | ||
|
|
cfb707d20f | ||
|
|
3aedc938cb | ||
|
|
c70e4c66e6 | ||
|
|
b727dbd604 | ||
|
|
cd7f237920 | ||
|
|
a1db763060 | ||
|
|
01e22740ce | ||
|
|
6b52b6b062 | ||
|
|
6781fd57af | ||
|
|
8b22c7812d | ||
|
|
80c1391361 | ||
|
|
c77e6b8616 | ||
|
|
e04ace4820 | ||
|
|
d89ea96b34 | ||
|
|
0e83e525fe | ||
|
|
d5d9a8bf03 | ||
|
|
ecb935868d | ||
|
|
d736d3c388 | ||
|
|
2520ccd6be | ||
|
|
4307dd043e | ||
|
|
4ae8e4252b | ||
|
|
995f03d03e | ||
|
|
2965f598ac | ||
|
|
0ad6f5aea1 | ||
|
|
c6fe8ff1f1 | ||
|
|
a3a6917bf8 | ||
|
|
1d8484957e | ||
|
|
0fc3fb4a58 | ||
|
|
6ab67ee844 | ||
|
|
11fa059b1b | ||
|
|
847115abd6 | ||
|
|
a5a0a310ce | ||
|
|
a72582b10f | ||
|
|
929e22dc5a | ||
|
|
ec4bca9bc0 | ||
|
|
0648541ce1 | ||
|
|
77529f29f5 | ||
|
|
02cf1739c0 | ||
|
|
903a5f5799 | ||
|
|
ccd955ca7e | ||
|
|
1af3b03fa8 | ||
|
|
cc28186f73 | ||
|
|
103bc8bd8b | ||
|
|
9bcf7af467 | ||
|
|
8d10980875 | ||
|
|
57d4945d71 | ||
|
|
11febb93bd | ||
|
|
cc9b83b0f8 | ||
|
|
54386924d7 | ||
|
|
35417bc05d | ||
|
|
8b212dd87f | ||
|
|
488035e124 | ||
|
|
9129edb725 | ||
|
|
6f3882e0ca | ||
|
|
f810f6aa85 | ||
|
|
6f13111c4c | ||
|
|
fcca1a1fa3 | ||
|
|
ac0aa9e33b | ||
|
|
6109ff7783 | ||
|
|
4d2d24efb3 | ||
|
|
e2b7081b9e | ||
|
|
6ba7fedc18 | ||
|
|
3973fae8ef | ||
|
|
77752f494f | ||
|
|
71574b62cb | ||
|
|
546ba16366 | ||
|
|
f61ca2ad17 | ||
|
|
781afd1c3b | ||
|
|
2bfd8495ab | ||
|
|
094fa65ad6 | ||
|
|
ec2a652d74 | ||
|
|
1b55645b81 | ||
|
|
5c02581c81 | ||
|
|
62cc4780ca | ||
|
|
8f26d57f40 | ||
|
|
3bd2fe889a | ||
|
|
492a2629c2 | ||
|
|
b64ddf32ff | ||
|
|
e86a550985 | ||
|
|
fcccdacb99 | ||
|
|
487de4c29b | ||
|
|
d36c6cb067 | ||
|
|
a4b704252b | ||
|
|
93e8d26a75 | ||
|
|
0a3ecaac64 | ||
|
|
ea6b762021 | ||
|
|
265a43e31c | ||
|
|
6d156586ac | ||
|
|
68e3f607a3 | ||
|
|
0b240a3902 | ||
|
|
4082761606 | ||
|
|
e68c42ba64 | ||
|
|
347546ee0e | ||
|
|
ca717d6b80 | ||
|
|
5d623d80eb | ||
|
|
a2edbd9934 | ||
|
|
5830f39d50 | ||
|
|
0aa0b59635 | ||
|
|
3c6a485647 | ||
|
|
bbe1fa8416 | ||
|
|
ac4469bb4a | ||
|
|
9e61e8eafa | ||
|
|
b193199268 | ||
|
|
f8b7d37af1 | ||
|
|
b3e9202d72 | ||
|
|
0f819ade56 | ||
|
|
d682afcde2 | ||
|
|
e67754ee9f | ||
|
|
4897771fbf | ||
|
|
4128f3404a | ||
|
|
7bc25ceb38 | ||
|
|
f77f0f5e04 | ||
|
|
0e666efaad | ||
|
|
e2893b9516 | ||
|
|
fb86b13948 | ||
|
|
e22cc6921d | ||
|
|
42fdd91206 | ||
|
|
2329d95002 | ||
|
|
8dda6ba13f | ||
|
|
156e2b8056 | ||
|
|
aa48324b6d | ||
|
|
d2b05ef7c3 | ||
|
|
75545e60b3 | ||
|
|
b3d0b30b56 | ||
|
|
3ed3729fc2 | ||
|
|
cb7ba46be6 | ||
|
|
933b21598e | ||
|
|
7a8442d9ea | ||
|
|
c405186533 | ||
|
|
5d150115a8 | ||
|
|
26bc38cf8c | ||
|
|
8f769cdda3 | ||
|
|
6b35cd3aee | ||
|
|
4ae1281a3a | ||
|
|
9984d4bc8f | ||
|
|
c987ee9fd3 | ||
|
|
a031e6f5f7 | ||
|
|
da386c3119 | ||
|
|
bfd29fc0fc | ||
|
|
9617776be1 | ||
|
|
765f2abc40 | ||
|
|
519bc42e0a | ||
|
|
6537e6bce6 | ||
|
|
494aff1bd3 | ||
|
|
927527b888 | ||
|
|
08054e4873 | ||
|
|
a092fef575 | ||
|
|
2808b3d3da | ||
|
|
ef3b236d73 | ||
|
|
26f0365026 | ||
|
|
25ebfee6e2 | ||
|
|
c1ada03db1 | ||
|
|
0b17360346 | ||
|
|
da18b37a54 | ||
|
|
4cf040c01e | ||
|
|
95b77515dc | ||
|
|
65bbd8ca05 | ||
|
|
dbba954522 | ||
|
|
26d46be243 | ||
|
|
135f8290ec | ||
|
|
a8b4e1178d | ||
|
|
8bdf66ef02 | ||
|
|
5209b08709 | ||
|
|
933247dc2f | ||
|
|
4e43bf9d8f | ||
|
|
0f6a5816db | ||
|
|
8021678995 | ||
|
|
147b5edec4 | ||
|
|
2edbf3222e | ||
|
|
f6bf8c229c | ||
|
|
c30b90af44 | ||
|
|
3b72e7af3d | ||
|
|
37004647eb | ||
|
|
c85cb84e31 | ||
|
|
d504143d7e | ||
|
|
db1c7b9708 | ||
|
|
064cb34705 | ||
|
|
02c442ef99 | ||
|
|
e358cafb3f | ||
|
|
40e447bc05 | ||
|
|
f0bac9ee78 | ||
|
|
a75fbad0ea | ||
|
|
ae5a919421 | ||
|
|
749cb7578a | ||
|
|
622ea20819 | ||
|
|
a9374a5eb1 | ||
|
|
1b78b68e33 | ||
|
|
3e97fe0490 | ||
|
|
2bb34607cc | ||
|
|
fe7fca180e | ||
|
|
0a86b45d59 | ||
|
|
62e0dd365b | ||
|
|
b39c561b72 | ||
|
|
02d099629f | ||
|
|
0f55cd2395 | ||
|
|
119451e4f6 | ||
|
|
0f6d4e5760 | ||
|
|
452ed6a754 | ||
|
|
40928b8ab0 | ||
|
|
f1ed380b42 | ||
|
|
e28ae8f2ba | ||
|
|
d8c6d9d558 | ||
|
|
607123f0be | ||
|
|
0ff72ae665 | ||
|
|
552cee1c24 | ||
|
|
44f05a2968 | ||
|
|
244fbf56d1 | ||
|
|
24845039f1 | ||
|
|
2e5be847ce | ||
|
|
9e947f7a36 | ||
|
|
87a3c60330 | ||
|
|
0d460d543b | ||
|
|
eb535cad2c | ||
|
|
eff00033b0 | ||
|
|
b591f97297 | ||
|
|
9501bb4428 | ||
|
|
4e9757c743 | ||
|
|
8f35b2ef71 | ||
|
|
3782eeeaf1 | ||
|
|
ec78de7a74 | ||
|
|
bd1f311d09 | ||
|
|
31bf36db89 | ||
|
|
a96d344c22 | ||
|
|
dac4988514 | ||
|
|
a1f50f152c | ||
|
|
06a47f4a06 | ||
|
|
f7aa9f7b92 | ||
|
|
894b11704c | ||
|
|
5b1b6ddc73 | ||
|
|
baa5abab2e | ||
|
|
1b2ae29f4a | ||
|
|
99a2b968d5 | ||
|
|
029672a907 | ||
|
|
31ecbaa977 | ||
|
|
6580e1b6db | ||
|
|
16bbe4ccc0 | ||
|
|
6ea3642700 | ||
|
|
b8270d2264 | ||
|
|
7b7fe1b062 | ||
|
|
e67c97cc21 | ||
|
|
1033214658 | ||
|
|
55552ad118 | ||
|
|
552c53b15d | ||
|
|
278fc30ae8 | ||
|
|
80fb676763 | ||
|
|
c511fd1895 | ||
|
|
e43a644eb0 | ||
|
|
70c16caf6b | ||
|
|
21dcfc9435 | ||
|
|
56ff019185 | ||
|
|
44a6cb31aa | ||
|
|
a01ebdaa9d | ||
|
|
651b742cc2 | ||
|
|
e2b1eb6e4a | ||
|
|
2546620c8d | ||
|
|
826f75348c | ||
|
|
b27462da05 | ||
|
|
ad0ade5dd4 | ||
|
|
6e3cf4630e | ||
|
|
6f3768db4b | ||
|
|
98220ccf93 | ||
|
|
52688d7145 | ||
|
|
b24edae66b | ||
|
|
ac39ce26ee | ||
|
|
cf041c661b | ||
|
|
9d173d0bdc | ||
|
|
aafb608149 | ||
|
|
04dd125ee6 | ||
|
|
e501235b2e | ||
|
|
7c6a322787 | ||
|
|
eed729181b | ||
|
|
ce9d42d304 | ||
|
|
733e31e54b | ||
|
|
f1714f3228 | ||
|
|
44c90f0de4 | ||
|
|
8268310685 | ||
|
|
d64eaf6234 | ||
|
|
4d0b848676 | ||
|
|
d508ca33c8 | ||
|
|
86fdea129b | ||
|
|
79af98845a | ||
|
|
2c15c6b0ed | ||
|
|
52ee10efda | ||
|
|
2da7d02a2c | ||
|
|
7af4ba27f3 | ||
|
|
46cd974a58 | ||
|
|
3d791b7f78 | ||
|
|
29e27c1283 | ||
|
|
8434ab460c | ||
|
|
561d073bba | ||
|
|
d46d37acb9 | ||
|
|
4f4d841137 | ||
|
|
b7e80ceee9 | ||
|
|
8a3112c71f | ||
|
|
4276b1439b | ||
|
|
3f626149f4 | ||
|
|
30b674f8f5 | ||
|
|
fe3fb46e94 | ||
|
|
99c524c7b6 | ||
|
|
5da7a582bf | ||
|
|
229c46a0c1 | ||
|
|
78d347889b | ||
|
|
244741ba15 | ||
|
|
015fa601a3 | ||
|
|
1bd9e44ecb | ||
|
|
dc7cfdb762 | ||
|
|
36afbae5af | ||
|
|
89d56a10f0 | ||
|
|
8dcc6031e6 | ||
|
|
b524a5424a | ||
|
|
f06ad99658 | ||
|
|
f4ad61a41b | ||
|
|
3c948aca96 | ||
|
|
57ee792198 | ||
|
|
6d84364cec | ||
|
|
d74fc16cfd | ||
|
|
56d72cb30b | ||
|
|
2f6f03ce8f | ||
|
|
b437ff3dcc | ||
|
|
93d17c2c35 | ||
|
|
b70ea04f02 | ||
|
|
89cde8b518 | ||
|
|
8d78623730 | ||
|
|
4a5b05f669 | ||
|
|
c4f2b7ed2e | ||
|
|
e296c77b14 | ||
|
|
56611c6e41 | ||
|
|
1e216b8cb3 | ||
|
|
302700ac80 | ||
|
|
cc5d943a86 | ||
|
|
7942f93649 | ||
|
|
270c6dd64b | ||
|
|
9f1633bc63 | ||
|
|
8953ec0e1f | ||
|
|
8f5dd3bc93 | ||
|
|
97ee6e6ee0 | ||
|
|
ae626294f3 | ||
|
|
842a45aa17 | ||
|
|
686936e7b2 | ||
|
|
8ac8ad6ed9 | ||
|
|
e7eda49676 | ||
|
|
faae8c7a77 | ||
|
|
5cb67a6e09 | ||
|
|
06747dc38d | ||
|
|
c3813fd87b | ||
|
|
901584a2e4 | ||
|
|
f9e33c7fca | ||
|
|
ea36ca37f0 | ||
|
|
2ea06f3096 | ||
|
|
dc208973c8 | ||
|
|
18ddb1001b | ||
|
|
567bc4a112 | ||
|
|
c5dbf5a412 | ||
|
|
c084e66db5 | ||
|
|
bb6d35f91f | ||
|
|
ac59bc2999 | ||
|
|
7dfb1d395d | ||
|
|
46672ca976 | ||
|
|
94e7e23cda | ||
|
|
2fa0ca41d6 | ||
|
|
2ebe35c6d6 | ||
|
|
354066de0b | ||
|
|
3d3c1f8f23 | ||
|
|
24448d604d | ||
|
|
7d8e530ebf | ||
|
|
5db88040cc | ||
|
|
d9de6e78d7 | ||
|
|
da136bfebd | ||
|
|
6657063441 | ||
|
|
47e6d4d711 | ||
|
|
32c7bd2627 | ||
|
|
3422d064ac | ||
|
|
0b01d40a85 | ||
|
|
7f79dc6098 | ||
|
|
ac7590f23c | ||
|
|
998bd3a1a9 | ||
|
|
0f62f1d72c | ||
|
|
cb1b1a5cac | ||
|
|
63ccdd0a23 | ||
|
|
ead809cc90 | ||
|
|
dfd4851bf1 | ||
|
|
e50f11697f | ||
|
|
4800ad53dc | ||
|
|
9349c3f055 | ||
|
|
d7a82ae911 | ||
|
|
b3354fb033 | ||
|
|
9b56b11c9b | ||
|
|
15247c3637 | ||
|
|
4311d3b057 | ||
|
|
076db0637f | ||
|
|
8a7108910d | ||
|
|
be12600094 | ||
|
|
988305100d | ||
|
|
16ae28ee62 | ||
|
|
30aab57bc9 | ||
|
|
a573914668 | ||
|
|
83771cc2e6 | ||
|
|
d8eaba75d2 | ||
|
|
96f917a1ab | ||
|
|
44d81a6bdf | ||
|
|
1b0e8440ae | ||
|
|
e236dbe661 | ||
|
|
2ba7d09c21 | ||
|
|
2b937689bd | ||
|
|
eeb7eb7ad2 | ||
|
|
c8e29211eb | ||
|
|
57cce7ec22 | ||
|
|
78af721250 | ||
|
|
cba6c2dacc | ||
|
|
b4b12cdbdc | ||
|
|
e7c86c95fe | ||
|
|
f28d96e91a | ||
|
|
b25217dd38 | ||
|
|
f60da8665b | ||
|
|
3d8daff050 | ||
|
|
c41f3c21c1 | ||
|
|
8e7897bf76 | ||
|
|
050bf75fdd | ||
|
|
1bbfcd0a7c | ||
|
|
30106d0006 | ||
|
|
16974068d7 | ||
|
|
08d0f67062 | ||
|
|
2edcb5863c | ||
|
|
44b9e49a54 | ||
|
|
0175903f51 | ||
|
|
0ef282f89c | ||
|
|
b4eec7d44c | ||
|
|
2e040a39db | ||
|
|
7e0ee705ff | ||
|
|
361005e242 | ||
|
|
2ea26f7319 | ||
|
|
44c4ca232f | ||
|
|
f7bb45288e | ||
|
|
c99aa41744 | ||
|
|
4eb1cc939c | ||
|
|
d4c51f18f5 | ||
|
|
af89c8bba7 | ||
|
|
2e951ef0db | ||
|
|
80b82366c4 | ||
|
|
3d75eae9c0 | ||
|
|
0bd8d9c4ca | ||
|
|
b3c1132a25 | ||
|
|
bddcb16e8a | ||
|
|
3cb2ba6fa3 | ||
|
|
205c8914ed | ||
|
|
76e0b67599 | ||
|
|
2c617b230a | ||
|
|
72d3268d39 | ||
|
|
a093a77610 | ||
|
|
a9c6443c18 | ||
|
|
757932ed4a | ||
|
|
55aad85bdd | ||
|
|
952d87f858 | ||
|
|
81fbe68e3b | ||
|
|
c82cd15f24 | ||
|
|
9991f7015f | ||
|
|
e94fac0459 | ||
|
|
816833c9f3 | ||
|
|
a202d72cd6 | ||
|
|
0558b0621a | ||
|
|
6e12b398d4 | ||
|
|
cce9aa5e97 | ||
|
|
6020122297 | ||
|
|
bed11116fe | ||
|
|
66cf93ffdd | ||
|
|
8f1ca7dee2 | ||
|
|
b24eac52ae | ||
|
|
5aaa3a00b4 | ||
|
|
0d737228b0 | ||
|
|
f57c862c3c | ||
|
|
ba476533ed | ||
|
|
28b39688ec | ||
|
|
fd8b1a8807 | ||
|
|
bff44b5d74 | ||
|
|
a9ee32d020 | ||
|
|
4fd1864eef | ||
|
|
3775279659 | ||
|
|
5bba633ab1 | ||
|
|
900cc0afe3 | ||
|
|
29fa64825e | ||
|
|
1c59404299 | ||
|
|
563613be6b | ||
|
|
5ae2296400 | ||
|
|
9534ceccd9 | ||
|
|
878845941d | ||
|
|
a5be2bb5b5 | ||
|
|
19c954f972 | ||
|
|
576f9db387 | ||
|
|
28ff02f8cb | ||
|
|
d4a590d292 | ||
|
|
25f928fc8a | ||
|
|
5dd3d927d6 | ||
|
|
77950632f8 | ||
|
|
e85cee752d | ||
|
|
5c48f13a27 | ||
|
|
18b283db0a | ||
|
|
ca7888a537 | ||
|
|
246e90acd1 | ||
|
|
e58e33974a | ||
|
|
5cb07d003c | ||
|
|
3ecedc8fcf | ||
|
|
a88ed5d3a9 | ||
|
|
37fead2076 | ||
|
|
2770c761b2 | ||
|
|
4b28eb6758 | ||
|
|
ba45b44f45 | ||
|
|
f5186cbadd | ||
|
|
807924c51e | ||
|
|
155d849201 | ||
|
|
e9efd2705c | ||
|
|
204e0d041c | ||
|
|
227d75d956 | ||
|
|
d2706c8748 | ||
|
|
2518b8600f | ||
|
|
9300839d59 | ||
|
|
a069d1f057 | ||
|
|
7557733b81 | ||
|
|
59ac3020fe | ||
|
|
f0e4cee613 | ||
|
|
0f395dad9e | ||
|
|
7e59362b5c | ||
|
|
e69492e94f | ||
|
|
2b31b173c0 | ||
|
|
8d3a1ab3a7 | ||
|
|
0349c5b402 | ||
|
|
0b7be8b0c6 | ||
|
|
3a8ca5fb4a | ||
|
|
c88b50ad89 | ||
|
|
3746ba1819 | ||
|
|
945bff3c17 | ||
|
|
d7e318b1f8 | ||
|
|
eb3db198a1 | ||
|
|
b380d32ce1 | ||
|
|
7a6056c95f | ||
|
|
a908a783be | ||
|
|
1ae02885cf | ||
|
|
8ea210e14f | ||
|
|
1ae9960e82 | ||
|
|
bed946988a | ||
|
|
e42632b82d | ||
|
|
5aa8425209 | ||
|
|
b794aa8089 | ||
|
|
e8bb0bffaa | ||
|
|
e1e0661f25 | ||
|
|
7848c83c72 | ||
|
|
2de37ed9c7 | ||
|
|
425b433cd2 | ||
|
|
44e07e3cdb | ||
|
|
689f882104 | ||
|
|
207beaee12 | ||
|
|
bd21b85de7 | ||
|
|
2e7579cac5 | ||
|
|
ec63812af0 | ||
|
|
8dfe1a22c9 | ||
|
|
c0e3f547ec | ||
|
|
dafb1b364c | ||
|
|
7d1b9d8635 | ||
|
|
8041f58ffd | ||
|
|
d018fdbace | ||
|
|
489602ffb3 | ||
|
|
600ba73f5f | ||
|
|
88600ae6df | ||
|
|
5e3bce9a92 | ||
|
|
9b28b7057d | ||
|
|
0a17220446 | ||
|
|
7684b19d13 | ||
|
|
47baf3b662 | ||
|
|
bc5e2b9437 | ||
|
|
cf0ef6d9d0 | ||
|
|
b6f3047f82 | ||
|
|
0a418561d7 | ||
|
|
6da79a41df | ||
|
|
3ddeac6c74 | ||
|
|
98f97c442d | ||
|
|
472d33ba12 | ||
|
|
d906cfcc1e | ||
|
|
0f0d8bdcdb | ||
|
|
005b2485f6 | ||
|
|
ffd90bed70 | ||
|
|
70264a24ab | ||
|
|
ad3a37fde3 | ||
|
|
9dac0eeaac | ||
|
|
8dac393bb4 | ||
|
|
287d1a5575 | ||
|
|
20278e0e53 | ||
|
|
a3c3ec8c5c | ||
|
|
3039b25a66 | ||
|
|
0b7ce92af8 | ||
|
|
e3ec521a1d | ||
|
|
88999263a7 | ||
|
|
9687ccd01d | ||
|
|
ca62502499 | ||
|
|
8b3a68f58e | ||
|
|
08eb35a360 | ||
|
|
dec82b41f2 | ||
|
|
b9ab5c76f7 | ||
|
|
6de0c536e8 | ||
|
|
3f0aca97bf | ||
|
|
ccb51a2603 | ||
|
|
99d9e70921 | ||
|
|
b8b54271dd | ||
|
|
49a1549e5a | ||
|
|
b3f936c194 | ||
|
|
f3b10d4c20 | ||
|
|
94b17037d0 | ||
|
|
83f3846ce6 | ||
|
|
711a751117 | ||
|
|
e013e32792 | ||
|
|
6e64b09256 | ||
|
|
e32d268587 | ||
|
|
05635dec93 | ||
|
|
e694923d33 | ||
|
|
7322683465 | ||
|
|
64688f3512 | ||
|
|
762760af2f | ||
|
|
48e6881401 | ||
|
|
95c958136f | ||
|
|
53a6be0f82 | ||
|
|
0b4d95be1c | ||
|
|
aad3948475 | ||
|
|
093370081e | ||
|
|
33426ceee9 | ||
|
|
5f5700b948 | ||
|
|
802e9abfef | ||
|
|
45149d6547 | ||
|
|
595681a982 | ||
|
|
e1cac723d6 | ||
|
|
683f84f053 | ||
|
|
b6d882a06f | ||
|
|
9d4bfbcb1c | ||
|
|
b563747046 | ||
|
|
1ebd0a430f | ||
|
|
ef4366be4d | ||
|
|
484283fef8 | ||
|
|
b546a48a5f | ||
|
|
51bd64d005 | ||
|
|
2c65d12b92 | ||
|
|
c43a3b5115 | ||
|
|
515778b13d | ||
|
|
bb4b9ce9e7 | ||
|
|
1d6707fecd | ||
|
|
5b2a46ed91 | ||
|
|
24fa97da4c | ||
|
|
b0109c2581 | ||
|
|
20f237ec4c | ||
|
|
6ea535ae29 | ||
|
|
57ca8a3eee | ||
|
|
307ea42a0f | ||
|
|
ac41c04f67 | ||
|
|
b3496e79de | ||
|
|
4f7aa755e3 | ||
|
|
d3af50f009 | ||
|
|
ba77a7b7c0 | ||
|
|
666a0e1537 | ||
|
|
96f4307910 | ||
|
|
81e997eb60 | ||
|
|
7132c6e7c2 | ||
|
|
cb01e92fe2 | ||
|
|
2c9c512d74 | ||
|
|
8d05c9040b | ||
|
|
4e994fb202 | ||
|
|
ab2a778694 | ||
|
|
115375820d | ||
|
|
8cb753916b | ||
|
|
ec3a0cee9b | ||
|
|
13f8f82537 | ||
|
|
54544dd2aa | ||
|
|
ac99115424 | ||
|
|
d6c6038193 | ||
|
|
c97b84e4a2 | ||
|
|
6698ec0b06 | ||
|
|
290b1c4de5 | ||
|
|
d497a50727 | ||
|
|
2837c91f5e | ||
|
|
9a717f28c6 | ||
|
|
e416c2c9c9 | ||
|
|
331cdaa6ce | ||
|
|
aa329b1a9a | ||
|
|
16c080942d | ||
|
|
4acc111b3d | ||
|
|
afe453d371 | ||
|
|
e93c120dac | ||
|
|
2f701458f4 | ||
|
|
6a35961dfb | ||
|
|
1a90095ba3 | ||
|
|
5dc87213b5 | ||
|
|
ff8044b09b | ||
|
|
f1fa40f6fd | ||
|
|
9bc5b62828 | ||
|
|
5aae586812 | ||
|
|
bc5e5be42e | ||
|
|
be68573378 | ||
|
|
cf0bc3d270 | ||
|
|
d5773eb2e9 | ||
|
|
50523e5b67 | ||
|
|
feeca86c6a | ||
|
|
a2e2671483 | ||
|
|
0b4d6ef8ef | ||
|
|
bdf0e532a5 | ||
|
|
d8cf72d09b | ||
|
|
6c3ec8f058 | ||
|
|
fbd9b76a09 | ||
|
|
bee216220a | ||
|
|
4ca7f34989 | ||
|
|
51015e6b51 | ||
|
|
46100d6d94 | ||
|
|
c94eac3863 | ||
|
|
965b0e5b6d | ||
|
|
000cea1dda | ||
|
|
6d4d60e3b1 | ||
|
|
9957282bfa | ||
|
|
9b603dd956 | ||
|
|
759ee55780 | ||
|
|
93b2edb5a5 | ||
|
|
4d49c14bb9 | ||
|
|
6398808a0a | ||
|
|
64e20f3665 | ||
|
|
3b542d8868 | ||
|
|
d5af0e43a5 | ||
|
|
7a8ef87dc7 | ||
|
|
7a5293cde3 | ||
|
|
26400631a0 | ||
|
|
97a5c584be | ||
|
|
0700b6573c | ||
|
|
d8f66a4188 | ||
|
|
c22971cf54 | ||
|
|
9d2dd5dd93 | ||
|
|
c19d512631 | ||
|
|
9efda5fef3 | ||
|
|
a27cdcfdb5 | ||
|
|
5667a07b25 | ||
|
|
7d0bd85466 | ||
|
|
4f269442a6 | ||
|
|
16d182fca2 | ||
|
|
6d1a206cc9 | ||
|
|
d8a7fdbf4f | ||
|
|
516b365ebc | ||
|
|
6da6a5ede9 | ||
|
|
67244f1957 | ||
|
|
0a304e9a03 | ||
|
|
05154af606 | ||
|
|
7454b15ec9 | ||
|
|
38f435f639 | ||
|
|
5f38ec7210 | ||
|
|
ca143bf969 | ||
|
|
402e5c3665 | ||
|
|
290dbcc3ed | ||
|
|
a7749c70e7 | ||
|
|
9f0cfd5f22 | ||
|
|
f47434c09e | ||
|
|
795a4b571e | ||
|
|
f71727e05e | ||
|
|
d2fa8e86b1 | ||
|
|
23dcd9d30f | ||
|
|
d6890c6ecd | ||
|
|
b1a6ba78d4 | ||
|
|
e6abe3f3a1 | ||
|
|
6c32540ecb | ||
|
|
951ad5cd0a | ||
|
|
5f56477123 | ||
|
|
3180f38a1d | ||
|
|
07f15993e5 | ||
|
|
fc8e9c7689 | ||
|
|
7cb20b9e28 | ||
|
|
ffc2351d64 | ||
|
|
f10fec61ac | ||
|
|
e41200067c | ||
|
|
30f6d3dde7 | ||
|
|
7f9dcb11f0 | ||
|
|
d5fa2f62c0 | ||
|
|
fa7967190d | ||
|
|
20e04c0d75 | ||
|
|
d5eea9db20 | ||
|
|
8cb598ce25 | ||
|
|
f3a57d7f23 | ||
|
|
14e47def9d | ||
|
|
7573081d7a | ||
|
|
aaca581324 | ||
|
|
43c2797a51 | ||
|
|
da6bbf65f0 | ||
|
|
7d0cbf423e | ||
|
|
d94eab71da | ||
|
|
b0a8ccc5b6 | ||
|
|
5cbf1f1568 | ||
|
|
22cde74db2 | ||
|
|
cdc3759c04 | ||
|
|
38abc8a167 | ||
|
|
7c7c7086ce | ||
|
|
50daf64025 | ||
|
|
dc38df5389 | ||
|
|
a46b2c9d98 | ||
|
|
200756361c | ||
|
|
877a1e4cd0 | ||
|
|
a382206f5a | ||
|
|
1af7a622a6 | ||
|
|
3f95b2aa7e | ||
|
|
b7871e2e04 | ||
|
|
6b6ceda497 | ||
|
|
b01b8e4bc8 | ||
|
|
20f7cb1cd0 | ||
|
|
3e1ee0056d | ||
|
|
a3a851e57f | ||
|
|
cf650d4318 | ||
|
|
58e7420f04 | ||
|
|
aedaf084d9 | ||
|
|
c18f638989 | ||
|
|
fb50cdc058 | ||
|
|
68e702c344 | ||
|
|
651b18a8b3 | ||
|
|
2174e56407 | ||
|
|
ea27a4da02 | ||
|
|
321fd1162b | ||
|
|
c51f38ddc7 | ||
|
|
b34cd1acc5 | ||
|
|
43f20bb83a | ||
|
|
79b8ddef15 | ||
|
|
2642ea0601 | ||
|
|
7a2f934f6a | ||
|
|
f299fefcdc | ||
|
|
209319f460 | ||
|
|
5ec4f4dcea | ||
|
|
16f67d58d1 | ||
|
|
542d794ab4 | ||
|
|
ede1573b56 | ||
|
|
bc8eec8887 | ||
|
|
59b0654b95 | ||
|
|
cae3477591 | ||
|
|
6a3115b80a | ||
|
|
1232d7c288 | ||
|
|
8e1b4c9271 | ||
|
|
0f8810e22a | ||
|
|
7bbff38b6b | ||
|
|
4a7b8fa250 | ||
|
|
53acc94976 | ||
|
|
ab8d83b12d | ||
|
|
713a0a6174 | ||
|
|
a3b9695b81 | ||
|
|
85dd381bd9 | ||
|
|
05b418e9b6 | ||
|
|
40af584afe | ||
|
|
93fe32491b | ||
|
|
4b67fcb0d4 | ||
|
|
0b0c92717d | ||
|
|
4564cafa85 | ||
|
|
2e21f8d03a | ||
|
|
4336254d56 | ||
|
|
bc5e9279a5 | ||
|
|
1bfbb956c2 | ||
|
|
b336db773c | ||
|
|
30e7d2424c | ||
|
|
cbb8c0234d | ||
|
|
95469107fd | ||
|
|
ba0058c0b9 | ||
|
|
8f35afe353 | ||
|
|
aaf219eb90 | ||
|
|
1167794d58 | ||
|
|
929babd69a | ||
|
|
870b7c1ffd | ||
|
|
9ed9d835b9 | ||
|
|
985feb4b91 | ||
|
|
c43c85bb21 | ||
|
|
038074131c | ||
|
|
ab9156a7d3 | ||
|
|
f4b76818e8 | ||
|
|
12a399a354 | ||
|
|
dfdbbea85c | ||
|
|
5fad5ec4c7 | ||
|
|
fe94da4727 | ||
|
|
361ae3af50 | ||
|
|
a2e302ebfa | ||
|
|
186ab9c00e | ||
|
|
9a2453524a | ||
|
|
3a2e128e98 | ||
|
|
1bd2dbf9f0 | ||
|
|
b7f880e7f8 | ||
|
|
13cd926b4f | ||
|
|
6fb7b1c6e4 | ||
|
|
8f35015a6c | ||
|
|
a926bccb97 | ||
|
|
b9f2abf5eb | ||
|
|
8dc5bda171 | ||
|
|
d24bdb543b | ||
|
|
dc921161e4 | ||
|
|
8e248cb545 | ||
|
|
ae93be4cfb | ||
|
|
42949fbe91 | ||
|
|
21dd175456 | ||
|
|
a3df0da26b | ||
|
|
6b9d4fd107 | ||
|
|
fac2ae6399 | ||
|
|
46732803c3 | ||
|
|
39d205bd4d | ||
|
|
8e2bd7997e | ||
|
|
76db9a6c62 | ||
|
|
c03d5e891d | ||
|
|
b21f6e8ce7 | ||
|
|
4b916f1888 | ||
|
|
1f80e5339b | ||
|
|
dc616f8ea1 | ||
|
|
dc6588243f | ||
|
|
690d3b0d4e | ||
|
|
4bc5dffabb | ||
|
|
55a1e4a544 | ||
|
|
5cb32dd75a | ||
|
|
f858c9fe48 | ||
|
|
0ac39af404 | ||
|
|
fc757b236f | ||
|
|
a921e6e3d4 | ||
|
|
8cf0f50565 | ||
|
|
575ad7664f | ||
|
|
be17488070 | ||
|
|
f504107928 | ||
|
|
0113f549c5 | ||
|
|
9ff831faab | ||
|
|
a9ca243c0b | ||
|
|
553bbef45f | ||
|
|
8f713f861b | ||
|
|
324054b9d7 | ||
|
|
d3fc9404ee | ||
|
|
5bb780439b | ||
|
|
f0e6008441 | ||
|
|
39dd69f15e | ||
|
|
7b2e89df26 | ||
|
|
aa853f8481 | ||
|
|
83bba75af6 | ||
|
|
23889aa5b1 | ||
|
|
5e6094fc42 | ||
|
|
a4181cb6d6 | ||
|
|
88c49f0722 | ||
|
|
513d9e199c | ||
|
|
e80975c56e | ||
|
|
9051ac102c | ||
|
|
39a9b2e619 | ||
|
|
dbc55d248f | ||
|
|
2b46180bfb | ||
|
|
5abe6c7e27 | ||
|
|
4595ebeb9a | ||
|
|
815c08e6d4 | ||
|
|
a72ad218a0 | ||
|
|
944ad1f769 | ||
|
|
e17a68f61c | ||
|
|
0b8196be68 | ||
|
|
d9c01148b7 | ||
|
|
2b3cfd992f | ||
|
|
7e4bd7a6f3 | ||
|
|
161e0ed637 | ||
|
|
1ee03e863c | ||
|
|
2411481d8b | ||
|
|
0314a627ed | ||
|
|
4d339a0b09 | ||
|
|
c171e7f94b | ||
|
|
eac4d966d9 | ||
|
|
c20868c20c | ||
|
|
bb74d46f1f | ||
|
|
152d48c583 | ||
|
|
b7a0155ba4 | ||
|
|
0414724b21 | ||
|
|
469f21db8d | ||
|
|
b6c9d30195 | ||
|
|
7b89430317 | ||
|
|
5bd854116a | ||
|
|
3fb7575529 | ||
|
|
a3a868c2ce | ||
|
|
1eb6fc2235 | ||
|
|
5ff4502f0a | ||
|
|
3126b8d4b0 | ||
|
|
dfb1d34b8a | ||
|
|
ace62c07be | ||
|
|
ec2f3024b6 | ||
|
|
25543f6561 | ||
|
|
3278657da7 | ||
|
|
90c4d12688 | ||
|
|
4ceaea99f7 | ||
|
|
585de34ef1 | ||
|
|
60e1a3ce93 | ||
|
|
413d77ff1a | ||
|
|
d8bb561063 | ||
|
|
5dff2c20b2 | ||
|
|
27803e7787 | ||
|
|
58f9c1575f | ||
|
|
7b909665b9 | ||
|
|
11eee991a1 | ||
|
|
7056a859ef | ||
|
|
2455f0d73e | ||
|
|
943d36e23e | ||
|
|
b563ac6e0c | ||
|
|
ed37a8ca2a | ||
|
|
55bee8b0d8 | ||
|
|
6bd18c29e3 | ||
|
|
37fc611767 | ||
|
|
f95fe351e2 | ||
|
|
2fb0ab1d91 | ||
|
|
85f7be1d79 | ||
|
|
b3e5c1abc2 | ||
|
|
9e216c0020 | ||
|
|
a9f00b6a8c | ||
|
|
46e7b02da2 | ||
|
|
8db0bc6e6f | ||
|
|
1702b5867a | ||
|
|
c03a6452ef | ||
|
|
69b840fb12 | ||
|
|
9385ab287f | ||
|
|
d2b2c39b97 | ||
|
|
f8ff978738 | ||
|
|
d50b32236c | ||
|
|
88d1db2840 | ||
|
|
b1dbe562d6 | ||
|
|
e95b6f1b5d | ||
|
|
1cbb7bb5dd | ||
|
|
34f3ffa129 | ||
|
|
ecd25a673e | ||
|
|
58d0778c3d | ||
|
|
81a5422974 | ||
|
|
eb859e5ede | ||
|
|
c8035b5071 | ||
|
|
a98a336e52 | ||
|
|
d7f6535b85 | ||
|
|
f9d4e3152b | ||
|
|
69bd816aeb | ||
|
|
783810749c | ||
|
|
92c3c967ba | ||
|
|
c632ba4306 | ||
|
|
e29d2c25ba | ||
|
|
5cfdc96cd2 | ||
|
|
a20e10b342 | ||
|
|
39b7414553 | ||
|
|
0eef20caf6 | ||
|
|
859d12b450 | ||
|
|
cd7c594b76 | ||
|
|
39862e15f6 | ||
|
|
87b0cf5c3f | ||
|
|
a14219e1e2 | ||
|
|
ee4660707c | ||
|
|
ccdf15c79c | ||
|
|
b31362afab | ||
|
|
74d6c88565 | ||
|
|
638f64b888 | ||
|
|
ffff9c1529 | ||
|
|
39968c7c6e | ||
|
|
2c3edf0ff2 | ||
|
|
2c4445a2d9 | ||
|
|
09114a5ed3 | ||
|
|
22e7825d65 | ||
|
|
62862141a8 | ||
|
|
920461920c | ||
|
|
f5d0e93ecd | ||
|
|
952a2f536b | ||
|
|
fbb9d849b4 | ||
|
|
65a13dee68 | ||
|
|
09d26b37c7 | ||
|
|
47de6905df | ||
|
|
c0cf316c16 | ||
|
|
c055f8069d | ||
|
|
575356214f | ||
|
|
4b5dc4152a | ||
|
|
a023409fe9 | ||
|
|
d36c59658d | ||
|
|
4f832ee70b | ||
|
|
7b97097515 | ||
|
|
86b3cb8d5d | ||
|
|
33e007ad48 | ||
|
|
efea9f0857 | ||
|
|
aa755a0093 | ||
|
|
807e80acbe | ||
|
|
4096247ee8 | ||
|
|
782d09ddbc | ||
|
|
4c304e11f3 | ||
|
|
aee497de03 | ||
|
|
823f8614e5 | ||
|
|
b54afb8823 | ||
|
|
22e611c63b | ||
|
|
3deb01a65d | ||
|
|
6f24b0bb71 | ||
|
|
9d7921f538 | ||
|
|
a4e66b3d11 | ||
|
|
3866ff76f9 | ||
|
|
82f947abdd | ||
|
|
47c6f53b43 | ||
|
|
606657524e | ||
|
|
168285944c | ||
|
|
1746af927b | ||
|
|
2eca8ffba2 | ||
|
|
6ef0c06a19 | ||
|
|
af68f1a16e | ||
|
|
f429839a17 | ||
|
|
66770e602a | ||
|
|
f760131cf5 | ||
|
|
cdc1d5064f | ||
|
|
3b707d29b9 | ||
|
|
df632bb1f0 | ||
|
|
74cb453995 | ||
|
|
f8eec94b04 | ||
|
|
697efacc58 | ||
|
|
ff9cff8f44 | ||
|
|
d78388646d | ||
|
|
05ffcdeef7 | ||
|
|
3b8e347a9e | ||
|
|
37f160f698 | ||
|
|
6f1446339c | ||
|
|
fed44dc366 | ||
|
|
c71ec34998 | ||
|
|
157bda8f51 | ||
|
|
d80e5bc792 | ||
|
|
f9669e8158 | ||
|
|
04c83c608b | ||
|
|
9f50037e11 | ||
|
|
33fd741ba7 | ||
|
|
035c3f9319 | ||
|
|
3df922340d | ||
|
|
72b9190e2f | ||
|
|
8fe1bdf39a | ||
|
|
f78bf2bc8a | ||
|
|
a1f0a69874 | ||
|
|
8a26b2f433 | ||
|
|
a18784d2c7 | ||
|
|
3c25a987fd | ||
|
|
7cebc4ef42 | ||
|
|
a8ab46d5c8 | ||
|
|
8bac5a8c54 | ||
|
|
801a41ae10 | ||
|
|
47e37afe12 | ||
|
|
942bf7d8cb | ||
|
|
0e8d573819 | ||
|
|
2bdfd7401e | ||
|
|
68dd6b1e23 | ||
|
|
2fe85ae0e5 | ||
|
|
e9fa1c3b5e | ||
|
|
f543b83cfc | ||
|
|
2d917ca11a | ||
|
|
2c830627ef | ||
|
|
46003a3509 | ||
|
|
6b0412586e | ||
|
|
36ca7ffbdb | ||
|
|
e5244f1fec | ||
|
|
359a7272e8 | ||
|
|
82cd3a7e56 | ||
|
|
a2e78fd08a | ||
|
|
a48b5e9d65 | ||
|
|
f4c4d9ea8c | ||
|
|
02ed45fa61 | ||
|
|
ad227ea2ee | ||
|
|
d7e2358d3e | ||
|
|
196e98e4a7 | ||
|
|
00bc45fba1 | ||
|
|
62c0d3f07f | ||
|
|
d42574a16c | ||
|
|
a854b04293 | ||
|
|
96632da4d7 | ||
|
|
cf18d6b1e9 | ||
|
|
262e2e1b85 | ||
|
|
743c46de70 | ||
|
|
9624bc96c9 | ||
|
|
1643bb7d59 | ||
|
|
edad5a838f | ||
|
|
fb35226e20 | ||
|
|
40605737b1 | ||
|
|
01a62e099e | ||
|
|
80748bb66b | ||
|
|
c6c77e9bc4 | ||
|
|
1f462648c4 | ||
|
|
099256e5d4 | ||
|
|
8735d24122 | ||
|
|
b2fc3967d3 | ||
|
|
0ec02cfa56 | ||
|
|
ade36d6876 | ||
|
|
73313d7dae | ||
|
|
7db9865ffa | ||
|
|
40656cb70b | ||
|
|
b5a07f3e79 | ||
|
|
66877ce9b1 | ||
|
|
57b067b4e2 | ||
|
|
74999e9394 | ||
|
|
f4d6f1e5ee | ||
|
|
096f670eb3 | ||
|
|
bceaca49bf | ||
|
|
5e94c84516 | ||
|
|
28dc14e739 | ||
|
|
d5d28e80cb | ||
|
|
f1eea76249 | ||
|
|
0976404eda | ||
|
|
8074c9a7c6 | ||
|
|
513af43fca | ||
|
|
afd5d76992 | ||
|
|
a31da5f880 | ||
|
|
be470f50ec | ||
|
|
a04494346e | ||
|
|
797533459e | ||
|
|
22e879ef18 | ||
|
|
dc6bc5b4d6 | ||
|
|
a6c7457c59 | ||
|
|
b609cfba2a | ||
|
|
345980f457 | ||
|
|
5a86a0a8cb | ||
|
|
24c1ce3f4a | ||
|
|
fc65c2583e | ||
|
|
1df3c33f69 | ||
|
|
98a9cc7436 | ||
|
|
ff88d4649c | ||
|
|
8c61306a8d | ||
|
|
6496e34868 | ||
|
|
7d6c9d517c | ||
|
|
d918d8ffd1 | ||
|
|
ca61ea09d0 | ||
|
|
faff230d42 | ||
|
|
c288c3d071 | ||
|
|
500a22b0b6 | ||
|
|
ba1317d245 | ||
|
|
5cfbfc5071 | ||
|
|
bf4b9d5355 | ||
|
|
904f0bc202 | ||
|
|
94c546bd0f | ||
|
|
64f1e10a5e | ||
|
|
8b20da78ff | ||
|
|
1be4ef437d | ||
|
|
76da9c3247 | ||
|
|
1e4dcc23d7 | ||
|
|
9c95e7fd96 | ||
|
|
cea5f57c60 | ||
|
|
19e6d37b62 | ||
|
|
018979d17f | ||
|
|
8bd398cbd6 | ||
|
|
59872b14cf | ||
|
|
c5240d72e1 | ||
|
|
77b54f9a10 | ||
|
|
b5bcb5b3bb | ||
|
|
87c500138b | ||
|
|
f3f384ebcc | ||
|
|
08de2eacd6 | ||
|
|
b763d968fe | ||
|
|
3b05eba2e2 | ||
|
|
b6d2c72a40 | ||
|
|
e6d2c9791d | ||
|
|
91a0eeb29a | ||
|
|
8c9c4865f2 | ||
|
|
6fbd4a7c88 | ||
|
|
239adaa129 | ||
|
|
9f8c82b649 | ||
|
|
7875bb6ebd | ||
|
|
a33abf3f56 | ||
|
|
1fadadcdb5 | ||
|
|
a294dec0dc | ||
|
|
5de75e6a8a | ||
|
|
4b44be2e54 | ||
|
|
b5f1d63175 | ||
|
|
1c5695df7e | ||
|
|
1808ad9ad7 | ||
|
|
8dc5e81374 | ||
|
|
b899056a8e | ||
|
|
f884c47ad8 | ||
|
|
07eae639b6 | ||
|
|
d2349bd713 | ||
|
|
28ee3c6e8f | ||
|
|
4430f269f7 | ||
|
|
a72f7d63f6 | ||
|
|
859b6f8ac5 | ||
|
|
a7f238aba3 | ||
|
|
e0cff27aae | ||
|
|
d9c4a6ec76 | ||
|
|
4d95bc4255 | ||
|
|
fdea0144b0 | ||
|
|
7398a373c2 | ||
|
|
c88e45cde8 | ||
|
|
f77848e3d9 | ||
|
|
5ed78cc3b6 | ||
|
|
1b4593bb62 | ||
|
|
c620e26644 | ||
|
|
74af24c3db | ||
|
|
ac1f32f773 | ||
|
|
99c7dbb332 | ||
|
|
b0632b8910 | ||
|
|
d26a9615c0 | ||
|
|
f609d6e0b2 | ||
|
|
e5042712b6 | ||
|
|
bf93d3af04 | ||
|
|
ea43abe392 | ||
|
|
0e74314ddc | ||
|
|
54eabd6a74 | ||
|
|
2f52473566 | ||
|
|
98e7250b3c | ||
|
|
882438f5e0 | ||
|
|
bb93dddd39 | ||
|
|
59f6a8a933 | ||
|
|
8ce83f3cd0 | ||
|
|
745e402da9 | ||
|
|
6cbe1c92d5 | ||
|
|
54fc2fb4c6 | ||
|
|
adc2815ab5 | ||
|
|
75f9f647ee | ||
|
|
f14f7dbb72 | ||
|
|
4978618e9a | ||
|
|
59b9f4f15b | ||
|
|
7ff0ce5369 | ||
|
|
93bd278819 | ||
|
|
b97b1f3690 | ||
|
|
307f374be1 | ||
|
|
17f701824e | ||
|
|
c7b89dcf71 | ||
|
|
fc527a947f | ||
|
|
d0520690a2 | ||
|
|
315d020b6f | ||
|
|
8a4bcdef52 | ||
|
|
727ef34c6d | ||
|
|
d8bc06d05d | ||
|
|
def214dde2 | ||
|
|
219f948c4a | ||
|
|
72193eccf0 | ||
|
|
7eb85c6a3d | ||
|
|
954c667173 | ||
|
|
33c738df5f | ||
|
|
2141fe298f | ||
|
|
8e73f512b4 | ||
|
|
92dddb8fd8 | ||
|
|
e9869f4c88 | ||
|
|
52597d4dcb | ||
|
|
dc7ce27b81 | ||
|
|
4242595a66 | ||
|
|
50b9a11503 | ||
|
|
621a723602 | ||
|
|
6260cc3655 | ||
|
|
300e668327 | ||
|
|
3cf274c37a | ||
|
|
2ca356ee5c | ||
|
|
ca8751c40c | ||
|
|
3c7e97d2d9 | ||
|
|
dbe2d690da | ||
|
|
949410e2cc | ||
|
|
100f856b5f | ||
|
|
d7bee01454 | ||
|
|
5566081986 | ||
|
|
29f7dd93f3 | ||
|
|
3dd8db5728 | ||
|
|
17e70d11f5 | ||
|
|
f8afd439b2 | ||
|
|
0dc61b1c26 | ||
|
|
de6c4fcb17 | ||
|
|
57f5c854ff | ||
|
|
4f4523ab77 | ||
|
|
e34e632519 | ||
|
|
f810b902a6 | ||
|
|
fd5bdc1484 | ||
|
|
e3a12ae6c5 | ||
|
|
c9c5664ec6 | ||
|
|
75cff549ce | ||
|
|
885cb6915d | ||
|
|
f9396a4ecf | ||
|
|
f1a03329e5 | ||
|
|
f65be671a3 | ||
|
|
fa9e8f6cba | ||
|
|
67a3e76cfe | ||
|
|
e4569c0bb8 | ||
|
|
6f1a62e917 | ||
|
|
3f6e21f15a | ||
|
|
46c4287c67 | ||
|
|
1ea1826a27 | ||
|
|
151457daf0 | ||
|
|
38cc21fa3a | ||
|
|
77f759f258 | ||
|
|
07c04061c2 | ||
|
|
d4bf6ed0c8 | ||
|
|
ebd46efe81 | ||
|
|
24d193efb0 | ||
|
|
3b2a63178f | ||
|
|
78f60afb85 | ||
|
|
52a7957789 | ||
|
|
6870583b1c | ||
|
|
9ffe514350 | ||
|
|
fc6e04497a | ||
|
|
cfb3f9a3ca | ||
|
|
d47fac791e | ||
|
|
490b70c2d3 | ||
|
|
3eecdd9ada | ||
|
|
7bdbc5417c | ||
|
|
2cd07dcb72 | ||
|
|
adb83ade67 | ||
|
|
a90f5da8b0 | ||
|
|
38937a4f83 | ||
|
|
31f35a23ee | ||
|
|
74de010d3c | ||
|
|
a9e4c880df | ||
|
|
b7fe265604 | ||
|
|
25c4af5a9f | ||
|
|
b3ecf702e1 | ||
|
|
c34a3cc7e7 | ||
|
|
158783f2d1 | ||
|
|
624192d301 | ||
|
|
e9be139ed0 | ||
|
|
2b9195ed9c | ||
|
|
dfdfecba57 | ||
|
|
2d6fec13fb | ||
|
|
216f340468 | ||
|
|
a4211a7e55 | ||
|
|
d6a2488fde | ||
|
|
d4d60439b6 | ||
|
|
75edd0d8ef | ||
|
|
b4e6e21094 | ||
|
|
8c48bc926e | ||
|
|
031b6fb30d | ||
|
|
647542e462 | ||
|
|
dbf28d83f3 | ||
|
|
142acdd42f | ||
|
|
ce103d30d3 | ||
|
|
556d65e8b4 | ||
|
|
e0cf070bd0 | ||
|
|
70c5a336c2 | ||
|
|
f82d5fbd2d | ||
|
|
31418ba6f7 | ||
|
|
3c10bb2669 | ||
|
|
0e009a2762 | ||
|
|
67dfd6b036 | ||
|
|
162eb0c958 | ||
|
|
a2dcc42f68 | ||
|
|
6b7f6dcb19 | ||
|
|
066e783a37 | ||
|
|
3e1bf001c7 | ||
|
|
722e18c07d | ||
|
|
a66ec00587 | ||
|
|
2b5100c019 | ||
|
|
74da05e6e8 | ||
|
|
af24e3b284 | ||
|
|
fb68d0bff3 | ||
|
|
d3a2ba26ba | ||
|
|
2f70ec5cb6 | ||
|
|
9173e9e56a | ||
|
|
11ad8a8398 | ||
|
|
0d518e44c7 | ||
|
|
7d8a5a2bdf |
242
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
# GitHub Copilot Instructions for mRemoteNG
|
||||
|
||||
## Project Overview
|
||||
|
||||
mRemoteNG is an open-source, multi-protocol, tabbed remote connections manager for Windows. It's a fork of mRemote that allows users to view and manage all their remote connections (RDP, VNC, SSH, Telnet, HTTP/HTTPS, rlogin, Raw Socket, PowerShell remoting, AnyDesk) in a simple yet powerful interface.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Language**: C# with latest language version
|
||||
- **Framework**: .NET 10.0 for Windows (net10.0-windows10.0.26100.0)
|
||||
- **UI Framework**: Windows Forms with WPF support
|
||||
- **Target Platforms**: x64 and ARM64
|
||||
- **Testing Framework**: NUnit with NSubstitute for mocking
|
||||
- **Build System**: MSBuild with .NET SDK-style projects
|
||||
|
||||
## Building the Project
|
||||
|
||||
### Prerequisites
|
||||
- Visual Studio 2022 (version 17.14.12 or later)
|
||||
- .NET 10.0 Desktop Runtime
|
||||
- Windows 10/11 or Windows Server 2016+
|
||||
|
||||
### Build Commands
|
||||
```powershell
|
||||
# Restore NuGet packages
|
||||
dotnet restore
|
||||
|
||||
# Build the solution
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
# Or for ARM64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=arm64
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
The project uses NUnit for testing. Test projects are in `mRemoteNGTests/` directory.
|
||||
|
||||
```powershell
|
||||
dotnet test mRemoteNGTests/mRemoteNGTests.csproj
|
||||
```
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Main Projects
|
||||
- **mRemoteNG**: Main application project
|
||||
- **mRemoteNGTests**: Unit and integration tests
|
||||
- **mRemoteNGSpecs**: Specification tests
|
||||
- **ObjectListView.NetCore**: Custom list view control
|
||||
- **ExternalConnectors**: External protocol connector implementations
|
||||
- **mRemoteNGDocumentation**: reStructuredText documentation
|
||||
|
||||
### Key Directories in mRemoteNG Project
|
||||
- `App/`: Application startup and initialization
|
||||
- `Connection/`: Connection models, protocols, and management
|
||||
- `Config/`: Configuration and serialization (XML, CSV)
|
||||
- `Container/`: Container/folder node implementations
|
||||
- `Credential/`: Credential storage and repositories
|
||||
- `Language/`: Localization resource files (.resx)
|
||||
- `Security/`: Authentication, encryption, and password management
|
||||
- `Tree/`: Connection tree UI and management
|
||||
- `UI/`: User interface components and forms
|
||||
- `Tools/`: Utility classes and helpers
|
||||
|
||||
## Code Style and Conventions
|
||||
|
||||
### EditorConfig Settings
|
||||
The project uses EditorConfig (`.editorconfig` in `mRemoteNG/` directory) with these key rules:
|
||||
|
||||
- **Indentation**: 4 spaces (no tabs)
|
||||
- **Line Endings**: CRLF (Windows-style)
|
||||
- **Charset**: UTF-8 with BOM for C# files
|
||||
- **New Lines**: Opening braces on new lines (Allman style)
|
||||
- **Using Directives**: Sort System directives first
|
||||
- **this. Qualifier**: Do not use `this.` prefix unless necessary
|
||||
|
||||
### Naming Conventions
|
||||
- **Classes/Interfaces**: PascalCase (e.g., `ConnectionInfo`, `IInheritable`)
|
||||
- **Methods**: PascalCase (e.g., `GetConnection`, `SaveToXml`)
|
||||
- **Properties**: PascalCase (e.g., `ConnectionName`, `Port`)
|
||||
- **Fields**: Use camelCase for private fields, consider `_` prefix for backing fields
|
||||
- **Constants**: PascalCase
|
||||
|
||||
### Code Patterns
|
||||
|
||||
#### Inheritance System
|
||||
mRemoteNG has a sophisticated property inheritance system for connection settings:
|
||||
|
||||
1. Properties can be inherited from parent containers/folders
|
||||
2. Each inheritable property has a corresponding `Inherit<PropertyName>` boolean in `ConnectionInfoInheritance` class
|
||||
3. Use `IInheritable` interface for objects that support inheritance
|
||||
4. Example: `ConnectionFrameColor` property has `InheritConnectionFrameColor` boolean
|
||||
|
||||
#### Serialization
|
||||
The project supports multiple serialization formats:
|
||||
|
||||
**XML Serialization** (`Config/Serializers/ConnectionSerializers/Xml/`):
|
||||
- Node-based serializers (e.g., `XmlConnectionNodeSerializer28.cs`)
|
||||
- Deserializers handle backward compatibility
|
||||
- Attributes for properties and inheritance flags
|
||||
- Always maintain backward compatibility - old files must still load
|
||||
|
||||
**CSV Serialization** (`Config/Serializers/ConnectionSerializers/Csv/`):
|
||||
- Export connections to CSV format
|
||||
- Include both value and inheritance columns
|
||||
- Maintain column order consistency
|
||||
|
||||
#### Localization
|
||||
All user-facing strings must be localized:
|
||||
|
||||
1. Add entries to `Language/Language.resx` (English base)
|
||||
2. Use `Language.ResourceName` to access strings in code
|
||||
3. Resource naming:
|
||||
- Properties: `PropertyName` (e.g., `ConnectionFrameColor`)
|
||||
- Descriptions: `PropertyDescription<PropertyName>`
|
||||
- Enum values: `<EnumName><Value>` (e.g., `FrameColorRed`)
|
||||
4. Provide clear, concise descriptions for PropertyGrid tooltips
|
||||
|
||||
### Common Patterns
|
||||
|
||||
#### Adding a New Connection Property
|
||||
|
||||
1. **Add enum** (if needed) in `Connection/` directory
|
||||
2. **Add property** to `AbstractConnectionRecord.cs` or `ConnectionInfo.cs`
|
||||
- Add `[Category("CategoryName")]` attribute for PropertyGrid grouping
|
||||
- Add `[Description("Language.PropertyDescription<Name>")]` for tooltip
|
||||
- Use appropriate TypeConverter if needed
|
||||
3. **Add inheritance support** in `ConnectionInfoInheritance.cs`
|
||||
4. **Update serializers**:
|
||||
- XML: Update latest `XmlConnectionNodeSerializer*.cs` and `XmlConnectionsDeserializer.cs`
|
||||
- CSV: Update `CsvConnectionsSerializerMremotengFormat.cs`
|
||||
5. **Add localization** in `Language/Language.resx`
|
||||
6. **Write tests** in `mRemoteNGTests/Connection/`
|
||||
7. **Update documentation** in `mRemoteNGDocumentation/` if user-facing
|
||||
|
||||
#### UI Controls
|
||||
- Prefer existing mRemoteNG patterns for UI controls
|
||||
- Connection panels use `InterfaceControl` base class
|
||||
- Custom painting: Override `OnPaint` or handle `Paint` event
|
||||
- Follow Windows Forms best practices
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
### Test Structure
|
||||
- Use NUnit's `[Test]` attribute for test methods
|
||||
- Use `[SetUp]` and `[TearDown]` for test initialization/cleanup
|
||||
- Use `[TestCase]` for parameterized tests
|
||||
- Use NSubstitute for mocking: `Substitute.For<IInterface>()`
|
||||
|
||||
### Test Naming
|
||||
- Use descriptive names: `MethodName_Scenario_ExpectedBehavior`
|
||||
- Example: `ConnectionInfo_SetPassword_EncryptsValue`
|
||||
|
||||
### Test Organization
|
||||
Mirror the main project structure in `mRemoteNGTests/`:
|
||||
- `Connection/` for connection tests
|
||||
- `Security/` for security tests
|
||||
- `Config/` for configuration/serialization tests
|
||||
|
||||
## Important Notes and Pitfalls
|
||||
|
||||
### Backward Compatibility
|
||||
- **Critical**: Never break loading of old connection files
|
||||
- Always provide default values for new properties
|
||||
- Test that files without new attributes still load correctly
|
||||
|
||||
### Security
|
||||
- Sensitive data (passwords, credentials) must be encrypted
|
||||
- Use existing security providers in `Security/` namespace
|
||||
- Never log or expose credentials in plain text
|
||||
|
||||
### Performance
|
||||
- Connection tree can contain thousands of nodes
|
||||
- Optimize for large connection files
|
||||
- Avoid unnecessary UI refreshes
|
||||
- Use async/await for I/O operations
|
||||
|
||||
### Platform-Specific Code
|
||||
- The project targets both x64 and ARM64
|
||||
- Avoid platform-specific code unless absolutely necessary
|
||||
- Test on both platforms when possible
|
||||
|
||||
### External Dependencies
|
||||
- PuTTY for SSH/Telnet (bundled)
|
||||
- Terminal Service Client for RDP
|
||||
- Various protocol-specific libraries (see CREDITS.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
### User Documentation
|
||||
- Located in `mRemoteNGDocumentation/`
|
||||
- Written in reStructuredText (.rst)
|
||||
- Follows ReadTheDocs format
|
||||
- Include screenshots and examples for new features
|
||||
|
||||
### Code Documentation
|
||||
- Use XML documentation comments for public APIs
|
||||
- Document complex algorithms and business logic
|
||||
- Keep implementation notes in `IMPLEMENTATION_NOTES.md` for significant features
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a Protocol
|
||||
1. Create protocol implementation in `Connection/Protocol/`
|
||||
2. Add protocol enum value to protocol types
|
||||
3. Implement connection logic
|
||||
4. Add UI controls if needed
|
||||
5. Update documentation
|
||||
|
||||
### Adding a Theme
|
||||
1. Add theme files to `Themes/` directory
|
||||
2. Update theme manager
|
||||
3. Add documentation in `mRemoteNGDocumentation/themes/`
|
||||
|
||||
### Updating Dependencies
|
||||
- Dependencies are centrally managed in `Directory.Packages.props`
|
||||
- Use `dotnet restore` after updating
|
||||
- Test thoroughly after dependency updates
|
||||
|
||||
## Version and Release
|
||||
|
||||
- Current development version: 1.78.2-dev
|
||||
- Version is set in `mRemoteNG.csproj`
|
||||
- Builds are automated via GitHub Actions (`.github/workflows/`)
|
||||
- Changelog maintained in `CHANGELOG.md`
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Project website: https://mremoteng.org
|
||||
- Documentation: https://mremoteng.readthedocs.io
|
||||
- GitHub Issues: Report bugs and feature requests
|
||||
- Community: Reddit r/mRemoteNG, Matrix chat
|
||||
|
||||
## Summary
|
||||
|
||||
When contributing to mRemoteNG:
|
||||
1. Follow the existing code style (EditorConfig)
|
||||
2. Maintain backward compatibility with old connection files
|
||||
3. Localize all user-facing strings
|
||||
4. Write tests for new functionality
|
||||
5. Update documentation for user-facing changes
|
||||
6. Test on both x64 and ARM64 if possible
|
||||
7. Keep security and performance in mind
|
||||
284
.github/workflows/Build_mR-NB.yml
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
name: Build_and_Release_mR-NB_MultiDeploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v1.78.2-dev
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_flag:
|
||||
description: 'Run NB release'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
NB-Build-and-Release:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Framework-Dependent builds (requires .NET runtime on user machine)
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
deployment: framework-dependent
|
||||
deploy_suffix: FD
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
deployment: framework-dependent
|
||||
deploy_suffix: FD
|
||||
# Self-Contained builds (includes .NET runtime)
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
deployment: self-contained
|
||||
deploy_suffix: SC
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
deployment: self-contained
|
||||
deploy_suffix: SC
|
||||
runs-on: ${{ matrix.runner }}
|
||||
# Only run if:
|
||||
# - manual dispatch, OR
|
||||
# - push event AND commit message contains "NB release"
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'push' && contains(github.event.head_commit.message, 'NB release'))
|
||||
|
||||
steps:
|
||||
- name: (01) Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: (02) Install .NET 10 SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: (03) Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
vs-version: '18.0'
|
||||
|
||||
- name: (04) Install and run dotnet-t4 to transform T4 templates
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global dotnet-t4
|
||||
# Refresh PATH to include global tools
|
||||
$env:PATH += ";$env:USERPROFILE\.dotnet\tools"
|
||||
$ttFile = "$env:GITHUB_WORKSPACE\mRemoteNG\Properties\AssemblyInfo.tt"
|
||||
# VS Enterprise 2022 assemblies
|
||||
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\PublicAssemblies"
|
||||
Write-Host "Transforming T4 template"
|
||||
t4 $ttFile -P platformType=${{ matrix.platform }} -r:"$vsPath\EnvDTE.dll" -r:"$vsPath\Microsoft.VisualStudio.Interop.dll"
|
||||
env:
|
||||
PLATFORM: '${{ matrix.platform }}'
|
||||
|
||||
- name: (05) Cache NuGet Packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-
|
||||
${{ runner.os }}-${{ matrix.arch }}-nuget-
|
||||
|
||||
- name: (06) Restore NuGet Packages
|
||||
shell: pwsh
|
||||
run: dotnet restore
|
||||
|
||||
- name: (07) Build Framework-Dependent Release
|
||||
if: matrix.deployment == 'framework-dependent'
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release" -p:Platform=${{ matrix.platform }} /verbosity:minimal
|
||||
|
||||
- name: (08) Build Self-Contained Release
|
||||
if: matrix.deployment == 'self-contained'
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" /t:Publish /p:Configuration="Release Self-Contained" -p:Platform=${{ matrix.platform }} /verbosity:minimal /p:SelfContained=true /p:PublishDir="bin\${{ matrix.platform }}\Release"
|
||||
|
||||
- name: (09) Release Information
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
|
||||
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
|
||||
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
|
||||
$version = $matches['ver']
|
||||
$build = $matches['build']
|
||||
} else {
|
||||
throw "Could not extract version and build number"
|
||||
}
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}-${{ matrix.deploy_suffix }}.zip"
|
||||
$tag = "$date-v$version-NB-($build)"
|
||||
$message = git log -1 --pretty=%B
|
||||
echo "message=$message" >> $env:GITHUB_OUTPUT
|
||||
echo "zipname=$zipName" >> $env:GITHUB_OUTPUT
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "build=$build" >> $env:GITHUB_OUTPUT
|
||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
echo "deployment=${{ matrix.deployment }}" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: (10) Extract Changelog Section
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
run: |
|
||||
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
|
||||
$lines = Get-Content $changelogPath
|
||||
|
||||
$startIndex = -1
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## \[') {
|
||||
$startIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($startIndex -eq -1) {
|
||||
throw "No version header found in CHANGELOG.md"
|
||||
}
|
||||
|
||||
$section = @()
|
||||
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## ') {
|
||||
break
|
||||
}
|
||||
$section += $lines[$i]
|
||||
}
|
||||
|
||||
$joined = $section -join "`n"
|
||||
echo "log<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $joined >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: (11) Create Zip File
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sourceDir = "$Env:GITHUB_WORKSPACE\mRemoteNG\bin\${{ matrix.platform }}\Release"
|
||||
Compress-Archive -Path "$sourceDir\*" -DestinationPath ${{ steps.version.outputs.zipname }}
|
||||
echo "File: ${{ steps.version.outputs.zipname }}"
|
||||
|
||||
# Show file size
|
||||
$fileSize = (Get-Item ${{ steps.version.outputs.zipname }}).Length / 1MB
|
||||
echo "Size: $([math]::Round($fileSize, 2)) MB"
|
||||
|
||||
- name: (10) Upload artifacts for combination
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-${{ matrix.arch }}-${{ matrix.deploy_suffix }}
|
||||
path: ${{ steps.version.outputs.zipname }}
|
||||
retention-days: 1
|
||||
|
||||
Create-Combined-Release:
|
||||
needs: NB-Build-and-Release
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Extract version info
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
|
||||
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
|
||||
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
|
||||
$version = $matches['ver']
|
||||
$build = $matches['build']
|
||||
} else {
|
||||
throw "Could not extract version and build number"
|
||||
}
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$tag = "$date-v$version-NB-($build)"
|
||||
$message = git log -1 --pretty=%B
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "build=$build" >> $env:GITHUB_OUTPUT
|
||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
echo "message=$message" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Extract Changelog
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
run: |
|
||||
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
|
||||
$lines = Get-Content $changelogPath
|
||||
|
||||
$startIndex = -1
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## \[') {
|
||||
$startIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($startIndex -eq -1) {
|
||||
throw "No version header found in CHANGELOG.md"
|
||||
}
|
||||
|
||||
$section = @()
|
||||
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## ') {
|
||||
break
|
||||
}
|
||||
$section += $lines[$i]
|
||||
}
|
||||
|
||||
$joined = $section -join "`n"
|
||||
echo "log<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $joined >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Collect all release files
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-ChildItem -Path artifacts -Recurse -Filter "*.zip" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination .
|
||||
echo "Found: $($_.Name)"
|
||||
}
|
||||
|
||||
- name: Create combined release
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
name: "mRemoteNG ${{ steps.version.outputs.version }} NB ${{ steps.version.outputs.build }}"
|
||||
files: '*.zip'
|
||||
body: |
|
||||
## mRemoteNG ${{ steps.version.outputs.version }} NB Build ${{ steps.version.outputs.build }}
|
||||
|
||||
### 📦 Available Downloads
|
||||
|
||||
**Framework-Dependent (FD)** - Requires .NET 10 Runtime installed:
|
||||
- Smaller download size (~15-25 MB)
|
||||
- Requires .NET 10 Desktop Runtime on user machine
|
||||
- Files: `*-FD.zip`
|
||||
|
||||
**Self-Contained (SC)** - Portable, no installation required:
|
||||
- Larger download size (~80-150 MB)
|
||||
- Includes .NET 10 runtime - no installation needed
|
||||
- Files: `*-SC.zip`
|
||||
|
||||
Both versions are available for **x64** and **ARM64** architectures.
|
||||
|
||||
---
|
||||
|
||||
### 📝 Changes in this Release
|
||||
${{ steps.changelog.outputs.log }}
|
||||
|
||||
### 💬 Last Commit Message
|
||||
${{ steps.version.outputs.message }}
|
||||
|
||||
draft: false
|
||||
prerelease: true
|
||||
126
.github/workflows/add_PR_2_chlog.yml
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
name: Update Changelog After Renovate PR Merge
|
||||
|
||||
on:
|
||||
# 1) Auto on pushes to your repo’s default branch (merge commits included)
|
||||
push:
|
||||
branches:
|
||||
- v1.78.2-dev
|
||||
|
||||
# 2) Manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dryRun:
|
||||
description: 'Run without committing changes'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
update-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Only proceed if…
|
||||
# - manual dispatch
|
||||
# - OR a push to default branch
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for Renovate dependency update
|
||||
id: check-renovate
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo "isRenovate=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
msg="$(git log -1 --pretty=%B)"
|
||||
if echo "$msg" | grep -q 'chore(deps): update dependency'; then
|
||||
echo "isRenovate=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "isRenovate=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Abort if not a Renovate PR
|
||||
if: steps.check-renovate.outputs.isRenovate == 'false'
|
||||
run: |
|
||||
echo "ℹ️ Last commit is not a Renovate dependency update—skipping."
|
||||
|
||||
- name: Parse Renovate PR info
|
||||
if: steps.check-renovate.outputs.isRenovate == 'true'
|
||||
id: extract
|
||||
shell: pwsh
|
||||
run: |
|
||||
# 1) Determine dryRun
|
||||
$dryRun = '${{ github.event.inputs.dryRun }}'
|
||||
if (-not $dryRun) { $dryRun = 'false' }
|
||||
Write-Host "🔍 dryRun = $dryRun"
|
||||
|
||||
# 2) Read full commit message
|
||||
$fullMsg = git log -1 --pretty=%B
|
||||
Write-Host "📝 Commit message:"; Write-Host $fullMsg
|
||||
|
||||
# 3) Extract PR number
|
||||
if ($fullMsg -match 'Merge pull request #(\d+)') {
|
||||
$prNumber = $matches[1]
|
||||
} else {
|
||||
throw "❌ Could not locate PR number in merge commit"
|
||||
}
|
||||
|
||||
# 4) Extract dependency name & version
|
||||
if ($fullMsg -match 'chore\(deps\): update dependency ([\w\.\-]+) to ([\d\.]+)') {
|
||||
$depName = $matches[1]
|
||||
$depVersion = $matches[2]
|
||||
} else {
|
||||
throw "❌ Could not parse dependency name/version"
|
||||
}
|
||||
|
||||
# 5) Export outputs
|
||||
echo "pr=$prNumber" >> $env:GITHUB_OUTPUT
|
||||
echo "depName=$depName" >> $env:GITHUB_OUTPUT
|
||||
echo "depVersion=$depVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "dryRun=$dryRun" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Update CHANGELOG.md
|
||||
if: steps.check-renovate.outputs.isRenovate == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$path = "$env:GITHUB_WORKSPACE/CHANGELOG.md"
|
||||
if (-not (Test-Path $path)) { throw "❌ CHANGELOG.md not found" }
|
||||
$lines = Get-Content $path
|
||||
$pr = '${{ steps.extract.outputs.pr }}'
|
||||
$dep = '${{ steps.extract.outputs.depName }}'
|
||||
$ver = '${{ steps.extract.outputs.depVersion }}'
|
||||
$entry = "- #$pr: update dependency $dep to $ver"
|
||||
|
||||
# Find latest version header: ## [x.y.z]
|
||||
$vIndex = $lines.FindIndex({ $_ -match '^## \[\d+\.\d+\.\d+\]' })
|
||||
if ($vIndex -lt 0) { throw "❌ No version header found" }
|
||||
|
||||
# Locate or create "### Dependency update"
|
||||
$depIndex = -1
|
||||
for ($i = $vIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^### Dependency update') { $depIndex = $i; break }
|
||||
if ($lines[$i] -match '^## ') { break }
|
||||
}
|
||||
if ($depIndex -eq -1) {
|
||||
$lines.Insert($vIndex + 1, '### Dependency update')
|
||||
$depIndex = $vIndex + 1
|
||||
}
|
||||
|
||||
# Insert the changelog entry
|
||||
$lines.Insert($depIndex + 1, $entry)
|
||||
Set-Content -Path $path -Value $lines
|
||||
Write-Host "✅ Inserted in CHANGELOG.md:"; Write-Host " $entry"
|
||||
|
||||
- name: Commit & push
|
||||
if: steps.check-renovate.outputs.isRenovate == 'true' && steps.extract.outputs.dryRun != 'true'
|
||||
run: |
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "docs: update changelog for #${{ steps.extract.outputs.pr }}"
|
||||
git push
|
||||
49
.github/workflows/post_2_Reddit.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Post to Reddit via PowerShell
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
post:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Authenticate and post to Reddit
|
||||
shell: pwsh
|
||||
env:
|
||||
CLIENT_ID: ${{ secrets.REDDIT_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.REDDIT_CLIENT_SECRET }}
|
||||
USERNAME: ${{ secrets.REDDIT_USERNAME }}
|
||||
PASSWORD: ${{ secrets.REDDIT_PASSWORD }}
|
||||
SUBREDDIT: ${{ secrets.REDDIT_SUBREDDIT }}
|
||||
run: |
|
||||
# Step 1: Get access token
|
||||
$authHeaders = @{
|
||||
Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$env:CLIENT_ID:$env:CLIENT_SECRET"))
|
||||
"User-Agent" = "github-to-reddit-pwsh/0.1"
|
||||
}
|
||||
|
||||
$authBody = @{
|
||||
grant_type = "password"
|
||||
username = $env:USERNAME
|
||||
password = $env:PASSWORD
|
||||
}
|
||||
|
||||
$authResponse = Invoke-RestMethod -Uri "https://www.reddit.com/api/v1/access_token" -Method Post -Headers $authHeaders -Body $authBody
|
||||
$token = $authResponse.access_token
|
||||
|
||||
# Step 2: Post to subreddit
|
||||
$postHeaders = @{
|
||||
Authorization = "bearer $token"
|
||||
"User-Agent" = "github-to-reddit-pwsh/0.1"
|
||||
}
|
||||
|
||||
$postBody = @{
|
||||
sr = $env:SUBREDDIT
|
||||
title = "Hello from GitHub Actions (PowerShell)"
|
||||
kind = "self"
|
||||
text = "This post was made using PowerShell in GitHub Actions."
|
||||
}
|
||||
|
||||
$postResponse = Invoke-RestMethod -Uri "https://oauth.reddit.com/api/submit" -Method Post -Headers $postHeaders -Body $postBody
|
||||
Write-Host "Posted: $($postResponse.json.url)"
|
||||
|
||||
6
.gitignore
vendored
@@ -283,6 +283,6 @@ Installer Projects/Installer/Fragments/HelpFilesFragment.wxs
|
||||
InstallerProjects/Installer/Fragments/FilesFragment.wxs
|
||||
InstallerProjects/Installer/Resources/License.rtf
|
||||
|
||||
# gh-pages info
|
||||
runlocal.bat
|
||||
/_site
|
||||
# mRemoteNG
|
||||
**/mRemoteNG/Properties/AssemblyInfo.tt
|
||||
**/mRemoteNG/Properties/AssemblyInfo.cs
|
||||
14
.readthedocs.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: mRemoteNGDocumentation/conf.py
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF
|
||||
formats:
|
||||
- pdf
|
||||
11
Add-ons/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## Add-ons library by 3rd party
|
||||
This is a list of add-ons, plugins and extentions what could be used with mRemoteNG, if you wish to add yours to this list - just drop me a line: <a href="mailto:support@mremoteng.org">support@mremoteng.org</a>
|
||||
<br>
|
||||
|
||||
| <b>Date added</b> | <b>Author</b> | <b>Type</b> | <b>Name</b> | <b>Description</b> | <b>Repository</b> |
|
||||
| :---------------------------------------------------------:|:-------------------------------------------------:| :---------: |-------------|--------------------|:-----------------:|
|
||||
| 02-08-2022 | <a href="https://github.com/JustBeta"><img align="left" src="https://avatars.githubusercontent.com/u/25150896?v=4" alt="JustBeta" width="30px"/>JustBeta</a> | script | Export-MobaXterm2mRemoteNG | Conversion of MobaXterm's ini file to mRemoteNG format. | [GITHUB Repository](https://github.com/JustBeta/Export-MobaXtern2mRemoteNG/tree/main) |
|
||||
|
||||
<br>
|
||||
|
||||
For a detailed usage examples and documentation please reach out authors.
|
||||
106
CHANGELOG.md
@@ -2,36 +2,130 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
## [1.78.2]
|
||||
### Fixed
|
||||
- #2939: fixed SQL injection vulnerabilities via parameterized queries
|
||||
- #2940: fixed for Possible command injection via Process.Start
|
||||
- #2932: fixed HTTP/HTTPS protocol to support multiple concurrent connections
|
||||
- #2855: fixed missing Username field for HTTP and HTTPS protocols
|
||||
- #2852: fixed XML External Entity (XXE) vulnerability in XML deserialization
|
||||
- #2851: fixed path traversal vulnerability in file operations
|
||||
- #2850: fixed password dialog appearing behind splash screen on startup
|
||||
- #2842: fixed element placement in options window
|
||||
- #2734: fixed native build for Windows-x64
|
||||
- #2715: fixed Disable WinForms analyzers and suppress WFO1000 build errors for ObjectListView
|
||||
- #2712: fixed VNCEvent_Disconnected send the ProtocolBase based object reference
|
||||
- #2668: fixed ssh quickconnect exception
|
||||
- #2611: fixed correct registry path
|
||||
- #2496: fixed use pwfile instead of cleartext password for putty connections
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- #1427: Fix RDP local desktop scale not taking effect on remote
|
||||
- #2931: added vault openbao connector
|
||||
- #2900: added "Report a Bug" menu item to Help menu and update bug report URL to GitHub issues
|
||||
- #2865: added configurable connection tab colors to distinguish between different environments
|
||||
- #2864: added color property to connections and folders with inheritance support
|
||||
- #2863: added ARD (Apple Remote Desktop) protocol support for macOS connections
|
||||
- #2728: added support for building mRemoteNG on Windows ARM64
|
||||
- #2723: added read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File
|
||||
- #2690: added தமிழ் (ta) Translation update
|
||||
- #2643: added registry Settings: enhancements and new settings implementation
|
||||
- #2591: add Clickstudios Passwordstate API connector
|
||||
- #2212: added 1Password integration
|
||||
|
||||
### Updated
|
||||
- #2883: Improve 1Password logging
|
||||
- #2854: Refactor settings dialog to be opened in dockable panel (for consistency)
|
||||
- #2597: Remember the opened connection file on relaunch
|
||||
- #2502: Updated Polish translation
|
||||
|
||||
### Dependency update
|
||||
- #3bd2fe8: puttyng updated to x64 version (and signed)
|
||||
|
||||
## [1.77.3.1784]
|
||||
### Fixed
|
||||
- #2362: Fix use of sql database
|
||||
- #2356: Improve speed for the display of the options page
|
||||
- #2352: SSH.NET Update
|
||||
- #2346: Modify "auto reconnect" to have the ability to really auto-reconnect
|
||||
- #2340: Set the default theme setting
|
||||
- #2339: Add 2 missing settings
|
||||
- #2261: Implement Show/Hide file menu in view menu
|
||||
- #2244: Save RCG and RestrictedAdmin fields correctly in connections file
|
||||
- #2195: Fix crafted XML File Code Execution vulnerability
|
||||
- #304: use pwfile instead of cleartext password for puttyng
|
||||
|
||||
### Added
|
||||
- #2285: Support extraction of SSH private keys from external cred prov
|
||||
- #2268: Postregsql database support
|
||||
|
||||
### Updated
|
||||
- #2295: Updates hyperlink style to make links more visible to end users
|
||||
- #2337: Set language.resx to auto generate the designer class
|
||||
|
||||
## [1.77.3]
|
||||
### Added
|
||||
- #1736: Update of SSH.NET to 2020.0.2 to allow File Transfer again
|
||||
- #2138: Improve compatibility with Remote Desktop Connection Manager v2.83
|
||||
- #2123: Thycotic Secret Server - Added 2FA OTP support
|
||||
### Changed
|
||||
- #1546: Enable resize without reconnect for RDP Version Rdc9 or higher
|
||||
|
||||
## [1.77.2]
|
||||
### Added
|
||||
- #2086: Replace WebClient with async HttpClient for updater.
|
||||
- #1850: Minify config xml
|
||||
- #1770: Added missing RDP performance settings
|
||||
- #1516: added API to access credential vault (Thycotic Secret Server) by specifying SSAPI:ID as username
|
||||
- #1476: Configurable backups. Can now edit/set backup frequency, backup path, and max number of backup files.
|
||||
- #1427: Fix RDP local desktop scale not taking effect on remote
|
||||
- #1332: Added option to hide menu strip container
|
||||
- #870: Added option to push inheritance settings to child nodes recursively
|
||||
- #545: Option to minimize to system tray on closing
|
||||
- #503: SSH Execute a single command after login
|
||||
- #420: SSH tunneling implemented
|
||||
- #327: Added Alternative Shell for RDP settings
|
||||
- #319: Override quick connect username when using user@domain
|
||||
- #283: Support for native PowerShell remoting as new protocol
|
||||
- #1850: Minify config xml
|
||||
- #xxx: Add external connector to retrieve ip address from Amazon EC2 Instance IDs
|
||||
### Changed
|
||||
- #2102: Extended the field RenderingEngine from 10 chars to 16
|
||||
- #2022: Replaced CefSharp with WebView2
|
||||
- #2014: Revised icons
|
||||
- #2013: Removed components check
|
||||
- #2011: Removed screenshot manager
|
||||
- #2010: Redesigned menus
|
||||
- #2005: Removed in-app documentation
|
||||
- #1777: Cleaned up VisualStudio project structure
|
||||
- #1767: Turned about window into a simple popup form
|
||||
- #1766: Converted components check page into options page
|
||||
- #1690: Replaced GeckoFX (Firefox) with CefSharp (Chromium)
|
||||
- #1325: Language resource files cleanup
|
||||
- #xxxx: Secret Server connector via new field "API User ID" instead of SSAPI: prefix
|
||||
### Fixed
|
||||
- #2125: Fixed string parsing logic for Quick Connect toolbar.
|
||||
- #2122: Fix to avoid throwing exception incase if not able decrypt connections and ask to open another one or create a new.
|
||||
- #2117: Fix of broken Links due migration to .NET 6 and branch renaming
|
||||
- #2098: Fix failed BinaryFileTest
|
||||
- #2097: Fix failed tests related to mRemoteNGTests.UI.Window.ConfigWindowTests
|
||||
- #2096: Corrected encryption code of LegacyRijndaelCryptographyProvider
|
||||
- #2089: Fixed the exception thrown by menu buttons "Documentation" and "Website"
|
||||
- #2087: Fixed application crash, when the update file is launched from the application
|
||||
- #2079: Fixed theme files not being copied to output directory
|
||||
- #2012: Updated PuTTYNG to v0.76
|
||||
- #1884: Allow setting Port when using MSSQL
|
||||
- #1783: Added missing inheritance properties to SQL scripts
|
||||
- #1773: Connection issue with mysql - Missing fields in
|
||||
- #1773: Connection issue with MySql - Missing fields in
|
||||
- #1756: Cannot type any character on MultiSSH toolbar
|
||||
- #1720: Show configuration file name in title of password prompt form
|
||||
- #1713: Sound redirection does not work if Clipboard redirection is set to No
|
||||
- #1632: 1.77.1 breaks RDP drive and sound redirection
|
||||
- #1610: Menu bar changes to english when cancelling options form
|
||||
- #1610: Menu bar changes to English when canceling options form
|
||||
- #1595: Unhandled exception when trying to browse through non existent multi ssh history with keyboard key strokes
|
||||
- #1589: Update SQL tables instead of rewriting them
|
||||
- #1465: REGRESSION: Smart Cards redirection to Remote Desktop not working
|
||||
- #1363: Don't show "Disk Usage" button in installer
|
||||
- #1337: Unhandled exception after closing mRemoteNG
|
||||
- #359: Making a VNC connection to an unreachable host causes the application to not respond for 20-30 seconds
|
||||
- #618: Do not break the Windows Clipboard Chain when exiting.
|
||||
|
||||
## [1.77.1] - 2019-09-02
|
||||
### Added
|
||||
|
||||
@@ -278,62 +278,3 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -1,7 +1,9 @@
|
||||
# Contributors
|
||||
|
||||
## Current mRemoteNG dev team
|
||||
[Dimitrij Gorodeckij](https://github.com/Kvarkas)
|
||||
|
||||
## Past Contributors
|
||||
[David Sparer](http://github.com/sparerd)
|
||||
[Sean Kaim](http://github.com/kmscode)
|
||||
[Faryan Rezagholi](http://github.com/farosch)
|
||||
@@ -28,8 +30,6 @@ Tony Lambert
|
||||
[MitchellBot](http://github.com/MitchellBot)
|
||||
[Filippo Ferrazini](http://github.com/Filippo125)
|
||||
|
||||
## Past Contributors
|
||||
|
||||
Felix Deimel - mRemote original developer
|
||||
Riley McArdle - mRemoteNG original developer
|
||||
|
||||
|
||||
167
DEPLOYMENT_OPTIONS.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# mRemoteNG Deployment Options
|
||||
|
||||
This document explains the two deployment options for mRemoteNG and how to build each version.
|
||||
|
||||
## Deployment Types
|
||||
|
||||
### 1. Framework-Dependent (FD)
|
||||
**File suffix: `-FD.zip`**
|
||||
|
||||
- **Size**: ~15-25 MB
|
||||
- **Requirements**: User must have .NET 10 Desktop Runtime installed
|
||||
- **Startup**: Application checks for .NET runtime and prompts user to download if missing
|
||||
- **Use case**: Standard release for users comfortable installing prerequisites
|
||||
|
||||
### 2. Self-Contained (SC)
|
||||
**File suffix: `-SC.zip`**
|
||||
|
||||
- **Size**: ~80-150 MB
|
||||
- **Requirements**: None - includes .NET 10 runtime
|
||||
- **Startup**: No runtime checks performed (runtime is bundled)
|
||||
- **Use case**: Portable version for users who want zero installation/configuration
|
||||
|
||||
## Building Locally
|
||||
|
||||
### Framework-Dependent Build
|
||||
|
||||
```powershell
|
||||
# x64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
# ARM64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=ARM64
|
||||
```
|
||||
|
||||
### Self-Contained Build
|
||||
|
||||
```powershell
|
||||
# x64
|
||||
dotnet publish mRemoteNG\mRemoteNG.csproj `
|
||||
--configuration Release `
|
||||
--runtime win-x64 `
|
||||
--self-contained true `
|
||||
-p:Platform=x64 `
|
||||
-p:PublishSingleFile=false `
|
||||
-p:PublishReadyToRun=true `
|
||||
-p:DefineConstants="SELF_CONTAINED"
|
||||
|
||||
# ARM64
|
||||
dotnet publish mRemoteNG\mRemoteNG.csproj `
|
||||
--configuration Release `
|
||||
--runtime win-arm64 `
|
||||
--self-contained true `
|
||||
-p:Platform=ARM64 `
|
||||
-p:PublishSingleFile=false `
|
||||
-p:PublishReadyToRun=true `
|
||||
-p:DefineConstants="SELF_CONTAINED"
|
||||
```
|
||||
|
||||
## GitHub Actions Workflow
|
||||
|
||||
The new workflow file `Build_and_Release_mR-NB-MultiDeploy.yml` automatically builds all four variants:
|
||||
|
||||
1. **x64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-FD.zip`
|
||||
2. **x64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-SC.zip`
|
||||
3. **ARM64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-FD.zip`
|
||||
4. **ARM64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-SC.zip`
|
||||
|
||||
### Workflow Triggers
|
||||
|
||||
The workflow runs when:
|
||||
- You push to `v1.78.2-dev` branch with commit message containing "NB release"
|
||||
- You manually trigger via workflow_dispatch
|
||||
|
||||
### Release Output
|
||||
|
||||
All four zip files are uploaded to a single GitHub Release with clear descriptions:
|
||||
- Framework-Dependent versions are marked as requiring .NET 10 Runtime
|
||||
- Self-Contained versions are marked as portable/no installation needed
|
||||
|
||||
## Code Changes
|
||||
|
||||
### ProgramRoot.cs
|
||||
|
||||
The `MainAsync` method now uses conditional compilation:
|
||||
|
||||
```csharp
|
||||
#if !SELF_CONTAINED
|
||||
// Runtime check code only included in Framework-Dependent builds
|
||||
// Checks for .NET Runtime and Visual C++ Redistributable
|
||||
#endif
|
||||
```
|
||||
|
||||
When building with `-p:DefineConstants="SELF_CONTAINED"`, the runtime checks are completely excluded from the compiled binary.
|
||||
|
||||
## Recommendations
|
||||
|
||||
### For Users
|
||||
|
||||
**Choose Framework-Dependent (FD) if:**
|
||||
- You don't mind installing .NET 10 Desktop Runtime once
|
||||
- You want smaller download size
|
||||
- You're using multiple .NET applications (runtime is shared)
|
||||
|
||||
**Choose Self-Contained (SC) if:**
|
||||
- You want zero installation/setup
|
||||
- You need portable deployment (USB drive, restricted environments)
|
||||
- You don't want to deal with prerequisites
|
||||
|
||||
### For Distribution
|
||||
|
||||
Consider offering both options:
|
||||
- Make Framework-Dependent the **default/recommended** option (smaller, faster updates)
|
||||
- Offer Self-Contained as **portable alternative** for special use cases
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Compilation Symbols
|
||||
|
||||
- Framework-Dependent builds: No special symbols
|
||||
- Self-Contained builds: `SELF_CONTAINED` symbol defined
|
||||
|
||||
### Runtime Identifiers
|
||||
|
||||
- x64: `win-x64`
|
||||
- ARM64: `win-arm64`
|
||||
|
||||
### Publish Options
|
||||
|
||||
Self-contained builds use these optimizations:
|
||||
- `PublishReadyToRun=true` - AOT compilation for faster startup
|
||||
- `IncludeNativeLibrariesForSelfExtract=true` - Bundle native dependencies
|
||||
- `PublishSingleFile=false` - Keep files separate for better compatibility with mRemoteNG's plugin system
|
||||
|
||||
## File Size Comparison
|
||||
|
||||
Typical build sizes:
|
||||
|
||||
| Version | Framework-Dependent | Self-Contained |
|
||||
|---------|---------------------|----------------|
|
||||
| x64 | ~18 MB | ~95 MB |
|
||||
| ARM64 | ~18 MB | ~95 MB |
|
||||
|
||||
*Note: Self-contained includes entire .NET 10 runtime (~80 MB overhead)*
|
||||
|
||||
## Testing
|
||||
|
||||
### Framework-Dependent Build
|
||||
1. Uninstall .NET 10 Runtime (if installed)
|
||||
2. Run mRemoteNG.exe
|
||||
3. Should prompt to download .NET 10 Runtime
|
||||
4. Install runtime and verify app launches
|
||||
|
||||
### Self-Contained Build
|
||||
1. Uninstall .NET 10 Runtime (if installed)
|
||||
2. Run mRemoteNG.exe
|
||||
3. Should launch immediately without runtime check
|
||||
4. Verify full functionality
|
||||
|
||||
## Migration from Old Workflow
|
||||
|
||||
The original workflow `Build_and_Release_mR-NB.yml` is preserved. To migrate:
|
||||
|
||||
1. Rename or remove old workflow: `Build_and_Release_mR-NB.yml`
|
||||
2. Rename new workflow: `Build_and_Release_mR-NB-MultiDeploy.yml` → `Build_and_Release_mR-NB.yml`
|
||||
3. Commit and push with "NB release" in message
|
||||
|
||||
|
||||
105
Directory.Packages.props
Normal file
@@ -0,0 +1,105 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<NoWarn>$(NoWarn);NU1507;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.14" />
|
||||
<PackageVersion Include="AWSSDK.EC2" Version="4.0.75.1" />
|
||||
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||
<PackageVersion Include="Castle.Core" Version="5.2.1" />
|
||||
<PackageVersion Include="ConsoleControl" Version="1.3.0" />
|
||||
<PackageVersion Include="ConsoleControlAPI" Version="1.3.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
|
||||
<PackageVersion Include="Cucumber.Messages" Version="32.0.1" />
|
||||
<PackageVersion Include="DockPanelSuite" Version="3.1.1" />
|
||||
<PackageVersion Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
|
||||
<PackageVersion Include="envdte" Version="17.14.40260" />
|
||||
<PackageVersion Include="Gherkin" Version="38.0.0" />
|
||||
<PackageVersion Include="Google.Protobuf" Version="3.33.5" />
|
||||
<PackageVersion Include="LiteDB" Version="5.0.21" />
|
||||
<PackageVersion Include="log4net" Version="3.2.0" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.TextTemplating.VSHost" Version="17.14.40265" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3800.47" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageVersion Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
|
||||
<PackageVersion Include="MySql.Data" Version="9.6.0" />
|
||||
<PackageVersion Include="NETStandard.Library" Version="2.0.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageVersion Include="NUnit" Version="4.4.0" />
|
||||
<PackageVersion Include="NUnit.Console" Version="3.22.0" />
|
||||
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.22.0" />
|
||||
<PackageVersion Include="NUnit.Extension.TeamCityEventListener" Version="1.0.10" />
|
||||
<PackageVersion Include="NUnit.Runners" Version="3.12.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
<PackageVersion Include="OpenCover" Version="4.7.1221" />
|
||||
<PackageVersion Include="Renci.SshNet.Async" Version="1.4.0" />
|
||||
<PackageVersion Include="ReportGenerator" Version="5.5.1" />
|
||||
<PackageVersion Include="runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.native.System" Version="4.3.1" />
|
||||
<PackageVersion Include="runtime.native.System.IO.Compression" Version="4.3.2" />
|
||||
<PackageVersion Include="runtime.native.System.Net.Http" Version="4.3.1" />
|
||||
<PackageVersion Include="runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Console" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.3" />
|
||||
<PackageVersion Include="System.DirectoryServices" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Dynamic.Runtime" Version="4.3.0" />
|
||||
<PackageVersion Include="System.IO.Pipelines" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Management" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Memory" Version="4.6.3" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Net.Primitives" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Net.Sockets" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Resources.ResourceManager" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Runtime" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
||||
<PackageVersion Include="System.Runtime.Extensions" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Security.AccessControl" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.OpenSsl" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Security.Permissions" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Windows.Extensions" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
|
||||
<PackageVersion Include="VaultSharp" Version="1.17.5.1" />
|
||||
<PackageVersion Include="VncSharpCore" Version="1.2.1" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.7" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
180
ExternalConnectors/AWS/AWSConnectionForm.Designer.cs
generated
Normal file
@@ -0,0 +1,180 @@
|
||||
namespace ExternalConnectors.AWS
|
||||
{
|
||||
partial class AWSConnectionForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.tbAccesKeyID = new System.Windows.Forms.TextBox();
|
||||
this.tbAccesKey = new System.Windows.Forms.TextBox();
|
||||
this.btnOK = new System.Windows.Forms.Button();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.tableLayoutPanel2.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tbAccesKeyID
|
||||
//
|
||||
this.tbAccesKeyID.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tbAccesKeyID.Location = new System.Drawing.Point(152, 4);
|
||||
this.tbAccesKeyID.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.tbAccesKeyID.Name = "tbAccesKeyID";
|
||||
this.tbAccesKeyID.Size = new System.Drawing.Size(490, 23);
|
||||
this.tbAccesKeyID.TabIndex = 0;
|
||||
//
|
||||
// tbAccesKey
|
||||
//
|
||||
this.tbAccesKey.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tbAccesKey.Location = new System.Drawing.Point(152, 43);
|
||||
this.tbAccesKey.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.tbAccesKey.Name = "tbAccesKey";
|
||||
this.tbAccesKey.Size = new System.Drawing.Size(490, 23);
|
||||
this.tbAccesKey.TabIndex = 2;
|
||||
//
|
||||
// btnOK
|
||||
//
|
||||
this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||
this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
this.btnOK.Location = new System.Drawing.Point(219, 12);
|
||||
this.btnOK.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.btnOK.Name = "btnOK";
|
||||
this.btnOK.Size = new System.Drawing.Size(88, 26);
|
||||
this.btnOK.TabIndex = 10;
|
||||
this.btnOK.Text = "OK";
|
||||
this.btnOK.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.btnCancel.Location = new System.Drawing.Point(338, 12);
|
||||
this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Size = new System.Drawing.Size(88, 26);
|
||||
this.btnCancel.TabIndex = 11;
|
||||
this.btnCancel.Text = "Cancel";
|
||||
this.btnCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
this.tableLayoutPanel1.ColumnCount = 2;
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 22.92994F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 77.07006F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.tbAccesKeyID, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.tbAccesKey, 1, 1);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
this.tableLayoutPanel1.RowCount = 3;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(646, 102);
|
||||
this.tableLayoutPanel1.TabIndex = 12;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label1.Location = new System.Drawing.Point(4, 0);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(140, 39);
|
||||
this.label1.TabIndex = 2;
|
||||
this.label1.Text = "Access Key ID";
|
||||
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label2.Location = new System.Drawing.Point(4, 39);
|
||||
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(140, 39);
|
||||
this.label2.TabIndex = 4;
|
||||
this.label2.Text = "Access Key";
|
||||
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
this.tableLayoutPanel2.ColumnCount = 5;
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 93F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 23F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 93F));
|
||||
this.tableLayoutPanel2.Controls.Add(this.btnOK, 1, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.btnCancel, 3, 0);
|
||||
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 112);
|
||||
this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
this.tableLayoutPanel2.RowCount = 1;
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel2.Size = new System.Drawing.Size(646, 50);
|
||||
this.tableLayoutPanel2.TabIndex = 13;
|
||||
//
|
||||
// AWSConnectionForm
|
||||
//
|
||||
this.AcceptButton = this.btnOK;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(646, 162);
|
||||
this.Controls.Add(this.tableLayoutPanel2);
|
||||
this.Controls.Add(this.tableLayoutPanel1);
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.Name = "AWSConnectionForm";
|
||||
this.Text = "AWS EC2 API Login Data";
|
||||
this.Activated += new System.EventHandler(this.AWSConnectionForm_Activated);
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
this.tableLayoutPanel1.PerformLayout();
|
||||
this.tableLayoutPanel2.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public System.Windows.Forms.TextBox tbAccesKeyID;
|
||||
public System.Windows.Forms.TextBox tbAccesKey;
|
||||
private System.Windows.Forms.Button btnOK;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||
}
|
||||
}
|
||||
16
ExternalConnectors/AWS/AWSConnectionForm.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace ExternalConnectors.AWS
|
||||
{
|
||||
public partial class AWSConnectionForm : Form
|
||||
{
|
||||
public AWSConnectionForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
}
|
||||
|
||||
private void AWSConnectionForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
tbAccesKeyID.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
ExternalConnectors/AWS/AWSConnectionForm.resx
Normal file
@@ -0,0 +1,60 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
119
ExternalConnectors/AWS/EC2FetchDataService.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Amazon;
|
||||
using Amazon.EC2;
|
||||
using Amazon.EC2.Model;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace ExternalConnectors.AWS
|
||||
{
|
||||
public class EC2FetchDataService
|
||||
{
|
||||
private static DateTime lastFetch;
|
||||
private static List<InstanceInfo>? lastData;
|
||||
|
||||
// input must be in format "AWSAPI:instanceid" where instanceid is the ec2 instance id, e.g. i-066f750a76c97583d
|
||||
public static async Task<string> GetEC2InstanceDataAsync(string input, string region)
|
||||
{
|
||||
// get secret id
|
||||
if (!input.StartsWith("AWSAPI:"))
|
||||
throw new Exception("calling this function requires AWSAPI: input");
|
||||
string InstanceID = input[7..];
|
||||
|
||||
// init connection credentials, display popup if necessary
|
||||
AWSConnectionData.Init();
|
||||
var alldata = await GetEC2IPDataAsync(region);
|
||||
var found = alldata.Where(x => x.InstanceId == InstanceID).SingleOrDefault();
|
||||
return (found == null) ? "" : found.PublicIP;
|
||||
}
|
||||
|
||||
private static async Task<List<InstanceInfo>> GetEC2IPDataAsync(string region)
|
||||
{
|
||||
// caching
|
||||
TimeSpan timeSpan = DateTime.Now - lastFetch;
|
||||
if (timeSpan.TotalMinutes < 1 && lastData != null)
|
||||
return lastData;
|
||||
|
||||
//AWSConfigs.AWSRegion = AWSConnectionData.region;
|
||||
AWSConfigs.AWSRegion = region;
|
||||
string awsAccessKeyId = AWSConnectionData.awsKeyID;
|
||||
string awsSecretAccessKey = AWSConnectionData.awsKey;
|
||||
|
||||
var _client = new AmazonEC2Client(awsAccessKeyId, awsSecretAccessKey, RegionEndpoint.EUCentral1);
|
||||
bool done = false;
|
||||
|
||||
List<InstanceInfo> instanceList = new();
|
||||
var request = new DescribeInstancesRequest();
|
||||
while (!done)
|
||||
{
|
||||
DescribeInstancesResponse response = await _client.DescribeInstancesAsync(request);
|
||||
|
||||
foreach (var reservation in response.Reservations)
|
||||
{
|
||||
foreach (var instance in reservation.Instances)
|
||||
{
|
||||
string vmname = "";
|
||||
foreach (var tag in instance.Tags)
|
||||
{
|
||||
if (tag.Key == "Name")
|
||||
{
|
||||
vmname = tag.Value;
|
||||
}
|
||||
}
|
||||
InstanceInfo inf = new(instance, vmname);
|
||||
instanceList.Add(inf);
|
||||
}
|
||||
}
|
||||
|
||||
request.NextToken = response.NextToken;
|
||||
|
||||
if (response.NextToken == null)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
lastData = instanceList.OrderBy(x => x.Name).ToList();
|
||||
lastFetch = DateTime.Now;
|
||||
return lastData;
|
||||
}
|
||||
|
||||
|
||||
public static class AWSConnectionData
|
||||
{
|
||||
private static readonly RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteAWSInterface");
|
||||
|
||||
public static string awsKeyID = "";
|
||||
public static string awsKey = "";
|
||||
//public static string _region = "eu-central-1";
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (awsKey != "")
|
||||
return;
|
||||
// display gui and ask for data
|
||||
AWSConnectionForm f = new();
|
||||
f.tbAccesKeyID.Text = "" + key.GetValue("KeyID");
|
||||
f.tbAccesKey.Text = "" + key.GetValue("Key");
|
||||
//f.tbRegion.Text = "" + key.GetValue("Region");
|
||||
//if (f.tbRegion.Text == null || f.tbRegion.Text.Length < 2)
|
||||
// f.tbRegion.Text = region;
|
||||
_ = f.ShowDialog();
|
||||
|
||||
if (f.DialogResult != DialogResult.OK)
|
||||
return;
|
||||
|
||||
// store values to memory
|
||||
awsKeyID = f.tbAccesKeyID.Text;
|
||||
awsKey = f.tbAccesKey.Text;
|
||||
//region = f.tbRegion.Text;
|
||||
|
||||
|
||||
// write values to registry
|
||||
key.SetValue("KeyID", awsKeyID);
|
||||
key.SetValue("Key", awsKey);
|
||||
//key.SetValue("Region", region);
|
||||
key.Close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
34
ExternalConnectors/AWS/InstanceInfo.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Amazon.EC2.Model;
|
||||
using System;
|
||||
|
||||
namespace ExternalConnectors.AWS
|
||||
{
|
||||
public class InstanceInfo
|
||||
{
|
||||
public string InstanceId { get; }
|
||||
public string Name { get; }
|
||||
public string Status { get; }
|
||||
public string PublicIP { get; }
|
||||
public string PrivateIP { get; }
|
||||
public InstanceInfo(Instance instance, string name)
|
||||
{
|
||||
InstanceId = instance.InstanceId;
|
||||
Name = name;
|
||||
|
||||
switch(instance.State.Code)
|
||||
{
|
||||
case 0: Status = "Pending"; break;
|
||||
case 16: Status = "Running"; break;
|
||||
case 32: Status = "Shutdown"; break;
|
||||
case 48: Status = "Terminated"; break;
|
||||
case 64: Status = "Stopping"; break;
|
||||
case 80: Status = "Stopped"; break;
|
||||
default: Status = "Unknown"; break;
|
||||
};
|
||||
|
||||
PublicIP = instance.PublicIpAddress ?? "";
|
||||
PrivateIP = instance.PrivateIpAddress ?? "";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
ExternalConnectors/CPS/CPS.ico
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
241
ExternalConnectors/CPS/CPSConnectionForm.Designer.cs
generated
Normal file
@@ -0,0 +1,241 @@
|
||||
namespace ExternalConnectors.CPS
|
||||
{
|
||||
partial class CPSConnectionForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CPSConnectionForm));
|
||||
tbServerURL = new TextBox();
|
||||
label3 = new Label();
|
||||
tbAPIKey = new TextBox();
|
||||
btnOK = new Button();
|
||||
btnCancel = new Button();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
label1 = new Label();
|
||||
label6 = new Label();
|
||||
tbOTP = new TextBox();
|
||||
cbUseSSO = new CheckBox();
|
||||
tableLayoutPanel2 = new TableLayoutPanel();
|
||||
label4 = new Label();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// tbServerURL
|
||||
//
|
||||
tbServerURL.Dock = DockStyle.Fill;
|
||||
tbServerURL.Location = new Point(298, 5);
|
||||
tbServerURL.Margin = new Padding(5);
|
||||
tbServerURL.Name = "tbServerURL";
|
||||
tbServerURL.Size = new Size(611, 27);
|
||||
tbServerURL.TabIndex = 0;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
label3.AutoSize = true;
|
||||
label3.Dock = DockStyle.Fill;
|
||||
label3.Location = new Point(5, 84);
|
||||
label3.Margin = new Padding(5, 0, 5, 0);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new Size(283, 42);
|
||||
label3.TabIndex = 5;
|
||||
label3.Text = "API Key";
|
||||
label3.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// tbAPIKey
|
||||
//
|
||||
tbAPIKey.Dock = DockStyle.Fill;
|
||||
tbAPIKey.Location = new Point(298, 89);
|
||||
tbAPIKey.Margin = new Padding(5);
|
||||
tbAPIKey.Name = "tbAPIKey";
|
||||
tbAPIKey.Size = new Size(611, 27);
|
||||
tbAPIKey.TabIndex = 4;
|
||||
tbAPIKey.UseSystemPasswordChar = true;
|
||||
//
|
||||
// btnOK
|
||||
//
|
||||
btnOK.Anchor = AnchorStyles.Right;
|
||||
btnOK.DialogResult = DialogResult.OK;
|
||||
btnOK.Location = new Point(337, 16);
|
||||
btnOK.Margin = new Padding(5);
|
||||
btnOK.Name = "btnOK";
|
||||
btnOK.Size = new Size(101, 35);
|
||||
btnOK.TabIndex = 6;
|
||||
btnOK.Text = "OK";
|
||||
btnOK.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
btnCancel.Anchor = AnchorStyles.Left;
|
||||
btnCancel.DialogResult = DialogResult.Cancel;
|
||||
btnCancel.Location = new Point(474, 16);
|
||||
btnCancel.Margin = new Padding(5);
|
||||
btnCancel.Name = "btnCancel";
|
||||
btnCancel.Size = new Size(101, 35);
|
||||
btnCancel.TabIndex = 11;
|
||||
btnCancel.Text = "Cancel";
|
||||
btnCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
tableLayoutPanel1.ColumnCount = 2;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 32.06997F));
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 67.93003F));
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label3, 0, 2);
|
||||
tableLayoutPanel1.Controls.Add(tbServerURL, 1, 0);
|
||||
tableLayoutPanel1.Controls.Add(tbAPIKey, 1, 2);
|
||||
tableLayoutPanel1.Controls.Add(label6, 0, 3);
|
||||
tableLayoutPanel1.Controls.Add(tbOTP, 1, 3);
|
||||
tableLayoutPanel1.Controls.Add(cbUseSSO, 0, 1);
|
||||
tableLayoutPanel1.Dock = DockStyle.Top;
|
||||
tableLayoutPanel1.Location = new Point(0, 0);
|
||||
tableLayoutPanel1.Margin = new Padding(5);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 5;
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
|
||||
tableLayoutPanel1.Size = new Size(914, 212);
|
||||
tableLayoutPanel1.TabIndex = 12;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.AutoSize = true;
|
||||
label1.Dock = DockStyle.Fill;
|
||||
label1.Location = new Point(5, 0);
|
||||
label1.Margin = new Padding(5, 0, 5, 0);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(283, 42);
|
||||
label1.TabIndex = 2;
|
||||
label1.Text = "Passwordstate URL";
|
||||
label1.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label6
|
||||
//
|
||||
label6.AutoSize = true;
|
||||
label6.Dock = DockStyle.Fill;
|
||||
label6.Location = new Point(3, 126);
|
||||
label6.Name = "label6";
|
||||
label6.Size = new Size(287, 42);
|
||||
label6.TabIndex = 15;
|
||||
label6.Text = "2FA OTP (Optional)";
|
||||
//
|
||||
// tbOTP
|
||||
//
|
||||
tbOTP.Dock = DockStyle.Fill;
|
||||
tbOTP.Location = new Point(298, 131);
|
||||
tbOTP.Margin = new Padding(5);
|
||||
tbOTP.Name = "tbOTP";
|
||||
tbOTP.Size = new Size(611, 27);
|
||||
tbOTP.TabIndex = 5;
|
||||
//
|
||||
// cbUseSSO
|
||||
//
|
||||
cbUseSSO.Anchor = AnchorStyles.Left;
|
||||
cbUseSSO.AutoSize = true;
|
||||
cbUseSSO.Location = new Point(5, 53);
|
||||
cbUseSSO.Margin = new Padding(5, 5, 5, 0);
|
||||
cbUseSSO.Name = "cbUseSSO";
|
||||
cbUseSSO.Size = new Size(157, 24);
|
||||
cbUseSSO.TabIndex = 14;
|
||||
cbUseSSO.Text = "Use SSO / WinAuth";
|
||||
cbUseSSO.UseVisualStyleBackColor = true;
|
||||
cbUseSSO.CheckedChanged += cbUseSSO_CheckedChanged;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
tableLayoutPanel2.ColumnCount = 5;
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 107F));
|
||||
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
|
||||
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||
tableLayoutPanel2.Location = new Point(0, 300);
|
||||
tableLayoutPanel2.Margin = new Padding(5);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel2.Size = new Size(914, 67);
|
||||
tableLayoutPanel2.TabIndex = 13;
|
||||
//
|
||||
// label4
|
||||
//
|
||||
label4.AutoSize = true;
|
||||
label4.Dock = DockStyle.Fill;
|
||||
label4.Location = new Point(0, 212);
|
||||
label4.Margin = new Padding(5, 0, 5, 0);
|
||||
label4.Name = "label4";
|
||||
label4.Size = new Size(345, 20);
|
||||
label4.TabIndex = 14;
|
||||
label4.Text = "URL is the base URL, like https://pass.domain.local/";
|
||||
label4.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// CPSConnectionForm
|
||||
//
|
||||
AcceptButton = btnOK;
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(914, 367);
|
||||
Controls.Add(label4);
|
||||
Controls.Add(tableLayoutPanel2);
|
||||
Controls.Add(tableLayoutPanel1);
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
Margin = new Padding(5);
|
||||
Name = "CPSConnectionForm";
|
||||
Text = "Passwordstate API Login Data";
|
||||
Activated += CPSConnectionForm_Activated;
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Label label3;
|
||||
|
||||
public System.Windows.Forms.TextBox tbServerURL;
|
||||
//public System.Windows.Forms.TextBox tbUsername;
|
||||
public System.Windows.Forms.TextBox tbAPIKey;
|
||||
private System.Windows.Forms.Button btnOK;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||
public System.Windows.Forms.CheckBox cbUseSSO;
|
||||
private System.Windows.Forms.Label label4;
|
||||
private Label label6;
|
||||
public TextBox tbOTP;
|
||||
}
|
||||
}
|
||||
41
ExternalConnectors/CPS/CPSConnectionForm.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace ExternalConnectors.CPS
|
||||
{
|
||||
public partial class CPSConnectionForm : Form
|
||||
{
|
||||
public CPSConnectionForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CPSConnectionForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
SetVisibility();
|
||||
if (cbUseSSO.Checked)
|
||||
btnOK.Focus();
|
||||
else
|
||||
{
|
||||
if (tbAPIKey.Text.Length == 0)
|
||||
tbAPIKey.Focus();
|
||||
else
|
||||
tbOTP.Focus();
|
||||
}
|
||||
|
||||
tbAPIKey.Focus();
|
||||
if (!string.IsNullOrEmpty(tbAPIKey.Text) || cbUseSSO.Checked == true)
|
||||
tbOTP.Focus();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
SetVisibility();
|
||||
}
|
||||
private void SetVisibility()
|
||||
{
|
||||
bool ch = cbUseSSO.Checked;
|
||||
tbAPIKey.Enabled = !ch;
|
||||
//tbUsername.Enabled = !ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -117,35 +117,33 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="resultsMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="portScanToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>161, 17</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAGxsbP9sbGz/EVuT/xRclP8XX5f/GmGZ/x1jm/8hZp7/JGig/yhro/8sbqb/MHGp/66u
|
||||
rv+3t7f/AAAAAAAAAAAAAAAAAAAAABVdlf90qNH/dqrS/3mr0/97rdT/fa/V/4Gy1v+Ds9f/hrbY/zZ1
|
||||
rf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZYJj/d6vT/1SVx/9Yl8j/W5rJ/1+dy/9ioM3/ZqLO/4u5
|
||||
2v88ebH/IGqqAiZqoJMbYpl3AAAAAAAAAAAAAAAAHmSc/3ut1P9Zmcn/XJvK/2CezP9koc3/Z6TP/3an
|
||||
yf+buMv/Tn+u/0OEv5t6qNL/MnKotwAAAAAAAAAAAAAAACNnn/9/sNX/XpzL/2KfzP9mos7/aaXP/4+u
|
||||
wP/bxK//682z/9y9o/+sr7b/UYzCvSJopBgAAAAAAAAAAAAAAAAoa6P/gbTX/1TC7f9bt+P/a6fQ/2+p
|
||||
0v/ezrz/9+PQ//Xcw//64cf/4cGm/WGCpCoAAAAAAAAAAAAAAAAAAAAALm+n/3y74P9L1P//X7rk/3Gr
|
||||
0v91rdT/8d3J//bizv/03MP/9t3E/+3Qtv/TpH4qAAAAAAAAAAAAAAAAAAAAADNzq/+Mutr/bavU/3Os
|
||||
0/93r9X/erLW/+LYy//46dr/9uPQ//jl0//qzLLr3bGNGQAAAAAAAAAAAAAAAAAAAAA5d6//j73c/3St
|
||||
1P95sNX/fLPX/4C22P+bwNX/49nN//Phzv/czcH/58KhSgAAAAAAAAAAAAAAAAAAAAAAAAAAP3y0/5TB
|
||||
3f96stb/frTX/4G32f+Futv/iL3c/4y/3f+oz+X/Y5XN/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWA
|
||||
uP+ZxOD/gLbY/4O42v+Hu9v/ir7c/43A3v+Qwt//q9Hn/2eY0P8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AABLhLz/ncfh/6DJ4v+iy+T/pc3k/6jP5f+q0ef/q9Po/67U6P9rm9P/AAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAUYjA/1WLw/9Zjsb/XJDI/2CTy/9jlc3/ZpfP/2ma0v9sm9P/bp3V/wAAAAAAAAAAAAAAAAAA
|
||||
AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA
|
||||
AAAtLDAA+PDaAP///wD79ugA/PrzAPf39wBGRUkA7NacAM2WAAA3z6kA+Pz/AIKBgwD+//4A4sNtAHXe
|
||||
xAD8+vIAjuTOANOjHgDV6/4AJZf3APn5+QDw37IAIB8jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA//+cQYABnEHgB5xB4ACcQeAAnEHgAJxB4AGcQeABnEHgAZxB4AOcQeAHnEHgB5xB4AecQeAH
|
||||
nEH//5xB//+cQQ==
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAgICAgICCQ4CAgIWAgIUAgICAgICAgkJAgICFhYWAAIIDwICAgICCQwCAhYWFgYCCAgIBwIC
|
||||
AgkJAgsWFhYWBQICCAgIAwIJCQIWFhYWAgICAgINCAgCAgICFhYCAgICAgICAgIRAgICAgICAgICAgIC
|
||||
AgICEwICAgICEhMTExMCAgITExMCAgICAgITExMTAhMTExMCAgICAgICAgICAhMTEwICAgIICAIJCQIC
|
||||
AgIKAgICAgIICAQCAgkJAgICAgICAgICCAgCAgICCQkCAgICAgICFQgBAgICAgwJAgICAgICAggIAgIC
|
||||
AgICEAkCAgICAgIIAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
295
ExternalConnectors/CPS/PasswordstateInterface.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
using Microsoft.Win32;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using Org.BouncyCastle.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ExternalConnectors.CPS;
|
||||
|
||||
public class PasswordstateInterface
|
||||
{
|
||||
private static class CPSConnectionData
|
||||
{
|
||||
public static string ssUsername = "";
|
||||
public static string ssPassword = "";
|
||||
public static string ssUrl = "";
|
||||
public static string ssOTP = "";
|
||||
public static DateTime ssOTPTimeStampExpiration;
|
||||
|
||||
public static bool ssSSO = false;
|
||||
public static bool initdone = false;
|
||||
|
||||
//token
|
||||
//public static string ssTokenBearer = "";
|
||||
//public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
|
||||
//public static string ssTokenRefresh = "";
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
// 2024-05-04 passwordstate currently does not support auth tokens, so we need to re-enter otp codes frequently
|
||||
if (!string.IsNullOrEmpty(ssOTP) && DateTime.Now > ssOTPTimeStampExpiration)
|
||||
{
|
||||
ssOTP = "";
|
||||
initdone = false;
|
||||
}
|
||||
|
||||
if (initdone == true)
|
||||
return;
|
||||
|
||||
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteCPSInterface");
|
||||
try
|
||||
{
|
||||
// display gui and ask for data
|
||||
CPSConnectionForm f = new CPSConnectionForm();
|
||||
//string? un = key.GetValue("Username") as string;
|
||||
//f.tbUsername.Text = un ?? "";
|
||||
f.tbAPIKey.Text = CPSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
|
||||
|
||||
string? url = key.GetValue("URL") as string;
|
||||
if (url == null || !url.Contains("://"))
|
||||
url = "https://cred.domain.local/SecretServer";
|
||||
f.tbServerURL.Text = url;
|
||||
|
||||
var b = key.GetValue("SSO");
|
||||
if (b == null || (string)b != "True")
|
||||
ssSSO = false;
|
||||
else
|
||||
ssSSO = true;
|
||||
f.cbUseSSO.Checked = ssSSO;
|
||||
|
||||
// show dialog
|
||||
while (true)
|
||||
{
|
||||
_ = f.ShowDialog();
|
||||
|
||||
if (f.DialogResult != DialogResult.OK)
|
||||
return;
|
||||
|
||||
// store values to memory
|
||||
//ssUsername = f.tbUsername.Text;
|
||||
ssPassword = f.tbAPIKey.Text;
|
||||
ssUrl = f.tbServerURL.Text;
|
||||
ssSSO = f.cbUseSSO.Checked;
|
||||
ssOTP = f.tbOTP.Text;
|
||||
ssOTPTimeStampExpiration = DateTime.Now.AddSeconds(30);
|
||||
// check connection first
|
||||
try
|
||||
{
|
||||
if (TestCredentials() == true)
|
||||
{
|
||||
initdone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
MessageBox.Show("Test Credentials failed - please check your credentials");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// write values to registry
|
||||
//key.SetValue("Username", ssUsername);
|
||||
key.SetValue("URL", ssUrl);
|
||||
key.SetValue("SSO", ssSSO);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
key.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TestCredentials()
|
||||
{
|
||||
return ConnectionTest();
|
||||
}
|
||||
private static bool ConnectionTest()
|
||||
{
|
||||
if (CPSConnectionData.ssSSO)
|
||||
{
|
||||
string url = $"{CPSConnectionData.ssUrl}/winapi/passwordlists/";
|
||||
|
||||
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||
|
||||
var json = client.GetStringAsync(url).Result;
|
||||
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||
if (data == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string url = $"{CPSConnectionData.ssUrl}/api/passwordlists/";
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
|
||||
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||
|
||||
var json = client.GetStringAsync(url).Result;
|
||||
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||
if (data == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonNode? FetchDataWinAuth(int secretID)
|
||||
{
|
||||
string url = $"{CPSConnectionData.ssUrl}/winapi/passwords/{secretID}";
|
||||
|
||||
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||
|
||||
var json = client.GetStringAsync(url).Result;
|
||||
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||
if (data == null)
|
||||
return null;
|
||||
JsonNode? element = data[0];
|
||||
return element;
|
||||
}
|
||||
private static JsonNode? FetchDataAPIKeyAuth(int secretID)
|
||||
{
|
||||
string url = $"{CPSConnectionData.ssUrl}/api/passwords/{secretID}";
|
||||
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
|
||||
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
|
||||
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
|
||||
|
||||
var json = client.GetStringAsync(url).Result;
|
||||
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
|
||||
if (data == null)
|
||||
return null;
|
||||
JsonNode? element = data[0];
|
||||
return element;
|
||||
}
|
||||
|
||||
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
|
||||
{
|
||||
// clear return variables
|
||||
secretDomain = "";
|
||||
secretUsername = "";
|
||||
secretPassword = "";
|
||||
privatekey = "";
|
||||
string privatekeypassphrase = "";
|
||||
JsonNode? element = null;
|
||||
|
||||
if (CPSConnectionData.ssSSO)
|
||||
element = FetchDataWinAuth(secretID);
|
||||
else
|
||||
element = FetchDataAPIKeyAuth(secretID);
|
||||
|
||||
if (element == null)
|
||||
return;
|
||||
|
||||
var dom = element["Domain"];
|
||||
if (dom != null) secretDomain = dom.ToString();
|
||||
|
||||
var user = element["UserName"];
|
||||
if (user != null) secretUsername = user.ToString();
|
||||
|
||||
var pw = element["Password"];
|
||||
if (pw != null) secretPassword = pw.ToString();
|
||||
|
||||
var privkey = element["GenericField1"];
|
||||
if (privkey != null) privatekey = privkey.ToString();
|
||||
|
||||
var phrase = element["GenericField3"];
|
||||
if (phrase != null) privatekeypassphrase = phrase.ToString();
|
||||
|
||||
// need to decode the private key?
|
||||
if (!string.IsNullOrEmpty(privatekeypassphrase))
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
|
||||
privatekey = key;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// conversion to putty format necessary?
|
||||
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
|
||||
{
|
||||
try
|
||||
{
|
||||
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
|
||||
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region PUTTY KEY HANDLING
|
||||
// decode rsa private key with encryption password
|
||||
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
|
||||
{
|
||||
TextReader textReader = new StringReader(encryptedPrivateKey);
|
||||
PemReader pemReader = new PemReader(textReader, new PasswordFinder(password));
|
||||
|
||||
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
|
||||
|
||||
TextWriter textWriter = new StringWriter();
|
||||
var pemWriter = new PemWriter(textWriter);
|
||||
pemWriter.WriteObject(keyPair.Private);
|
||||
pemWriter.Writer.Flush();
|
||||
|
||||
return ""+textWriter.ToString();
|
||||
}
|
||||
private class PasswordFinder(string password) : IPasswordFinder
|
||||
{
|
||||
private string password = password;
|
||||
|
||||
public char[] GetPassword()
|
||||
{
|
||||
return password.ToCharArray();
|
||||
}
|
||||
}
|
||||
|
||||
// read private key pem string to rsacryptoserviceprovider
|
||||
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
|
||||
{
|
||||
PemReader pr = new PemReader(new StringReader(pem));
|
||||
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
|
||||
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
|
||||
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
|
||||
rsa.ImportParameters(rsaParams);
|
||||
return rsa;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
// input: must be the secret id to fetch
|
||||
public static void FetchSecretFromServer(string secretID, out string username, out string password, out string domain, out string privatekey)
|
||||
{
|
||||
// get secret id
|
||||
int sid = Int32.Parse(secretID);
|
||||
|
||||
// init connection credentials, display popup if necessary
|
||||
CPSConnectionData.Init();
|
||||
|
||||
// get the secret
|
||||
FetchSecret(sid, out username, out password, out domain, out privatekey);
|
||||
}
|
||||
}
|
||||
BIN
ExternalConnectors/DSS/DSS.ico
Normal file
|
After Width: | Height: | Size: 32 KiB |
281
ExternalConnectors/DSS/SSConnectionForm.Designer.cs
generated
Normal file
@@ -0,0 +1,281 @@
|
||||
namespace ExternalConnectors.DSS
|
||||
{
|
||||
partial class SSConnectionForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SSConnectionForm));
|
||||
this.tbSSURL = new System.Windows.Forms.TextBox();
|
||||
this.tbUsername = new System.Windows.Forms.TextBox();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
this.tbPassword = new System.Windows.Forms.TextBox();
|
||||
this.btnOK = new System.Windows.Forms.Button();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.label5 = new System.Windows.Forms.Label();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.cbUseSSO = new System.Windows.Forms.CheckBox();
|
||||
this.label6 = new System.Windows.Forms.Label();
|
||||
this.tbOTP = new System.Windows.Forms.TextBox();
|
||||
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.tableLayoutPanel2.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tbSSURL
|
||||
//
|
||||
this.tbSSURL.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tbSSURL.Location = new System.Drawing.Point(298, 5);
|
||||
this.tbSSURL.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.tbSSURL.Name = "tbSSURL";
|
||||
this.tbSSURL.Size = new System.Drawing.Size(611, 27);
|
||||
this.tbSSURL.TabIndex = 0;
|
||||
//
|
||||
// tbUsername
|
||||
//
|
||||
this.tbUsername.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tbUsername.Location = new System.Drawing.Point(298, 47);
|
||||
this.tbUsername.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.tbUsername.Name = "tbUsername";
|
||||
this.tbUsername.Size = new System.Drawing.Size(611, 27);
|
||||
this.tbUsername.TabIndex = 2;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label3.Location = new System.Drawing.Point(5, 84);
|
||||
this.label3.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(283, 42);
|
||||
this.label3.TabIndex = 5;
|
||||
this.label3.Text = "Password";
|
||||
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// tbPassword
|
||||
//
|
||||
this.tbPassword.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tbPassword.Location = new System.Drawing.Point(298, 89);
|
||||
this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.tbPassword.Name = "tbPassword";
|
||||
this.tbPassword.Size = new System.Drawing.Size(611, 27);
|
||||
this.tbPassword.TabIndex = 4;
|
||||
this.tbPassword.UseSystemPasswordChar = true;
|
||||
//
|
||||
// btnOK
|
||||
//
|
||||
this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||
this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
this.btnOK.Location = new System.Drawing.Point(337, 16);
|
||||
this.btnOK.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.btnOK.Name = "btnOK";
|
||||
this.btnOK.Size = new System.Drawing.Size(101, 35);
|
||||
this.btnOK.TabIndex = 6;
|
||||
this.btnOK.Text = "OK";
|
||||
this.btnOK.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.btnCancel.Location = new System.Drawing.Point(474, 16);
|
||||
this.btnCancel.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Size = new System.Drawing.Size(101, 35);
|
||||
this.btnCancel.TabIndex = 11;
|
||||
this.btnCancel.Text = "Cancel";
|
||||
this.btnCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
this.tableLayoutPanel1.ColumnCount = 2;
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 32.06997F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 67.93003F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.label5, 1, 4);
|
||||
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.label3, 0, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.tbSSURL, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.cbUseSSO, 0, 4);
|
||||
this.tableLayoutPanel1.Controls.Add(this.tbUsername, 1, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.tbPassword, 1, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.label6, 0, 3);
|
||||
this.tableLayoutPanel1.Controls.Add(this.tbOTP, 1, 3);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
this.tableLayoutPanel1.RowCount = 5;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(914, 212);
|
||||
this.tableLayoutPanel1.TabIndex = 12;
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.AutoSize = true;
|
||||
this.label5.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label5.Location = new System.Drawing.Point(298, 168);
|
||||
this.label5.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Size = new System.Drawing.Size(611, 44);
|
||||
this.label5.TabIndex = 15;
|
||||
this.label5.Text = "For SSO to work, additional IIS configuration is required!";
|
||||
this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label1.Location = new System.Drawing.Point(5, 0);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(283, 42);
|
||||
this.label1.TabIndex = 2;
|
||||
this.label1.Text = "Secret Server URL";
|
||||
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label2.Location = new System.Drawing.Point(5, 42);
|
||||
this.label2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(283, 42);
|
||||
this.label2.TabIndex = 4;
|
||||
this.label2.Text = "DOMAIN\\Username";
|
||||
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// cbUseSSO
|
||||
//
|
||||
this.cbUseSSO.AutoSize = true;
|
||||
this.cbUseSSO.Location = new System.Drawing.Point(5, 173);
|
||||
this.cbUseSSO.Margin = new System.Windows.Forms.Padding(5, 5, 5, 0);
|
||||
this.cbUseSSO.Name = "cbUseSSO";
|
||||
this.cbUseSSO.Size = new System.Drawing.Size(86, 24);
|
||||
this.cbUseSSO.TabIndex = 14;
|
||||
this.cbUseSSO.Text = "Use SSO";
|
||||
this.cbUseSSO.UseVisualStyleBackColor = true;
|
||||
this.cbUseSSO.CheckedChanged += new System.EventHandler(this.cbUseSSO_CheckedChanged);
|
||||
//
|
||||
// label6
|
||||
//
|
||||
this.label6.AutoSize = true;
|
||||
this.label6.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label6.Location = new System.Drawing.Point(3, 126);
|
||||
this.label6.Name = "label6";
|
||||
this.label6.Size = new System.Drawing.Size(287, 42);
|
||||
this.label6.TabIndex = 15;
|
||||
this.label6.Text = "2FA OTP (Optional)";
|
||||
//
|
||||
// tbOTP
|
||||
//
|
||||
this.tbOTP.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tbOTP.Location = new System.Drawing.Point(296, 130);
|
||||
this.tbOTP.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.tbOTP.Name = "tbOTP";
|
||||
this.tbOTP.Size = new System.Drawing.Size(615, 27);
|
||||
this.tbOTP.TabIndex = 5;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
this.tableLayoutPanel2.ColumnCount = 5;
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 106F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 26F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 107F));
|
||||
this.tableLayoutPanel2.Controls.Add(this.btnOK, 1, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.btnCancel, 3, 0);
|
||||
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 300);
|
||||
this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
this.tableLayoutPanel2.RowCount = 1;
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel2.Size = new System.Drawing.Size(914, 67);
|
||||
this.tableLayoutPanel2.TabIndex = 13;
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label4.Location = new System.Drawing.Point(0, 212);
|
||||
this.label4.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(427, 20);
|
||||
this.label4.TabIndex = 14;
|
||||
this.label4.Text = "URL is the base URL, like https://cred.domain.local/SecretServer";
|
||||
this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// SSConnectionForm
|
||||
//
|
||||
this.AcceptButton = this.btnOK;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(914, 367);
|
||||
this.Controls.Add(this.label4);
|
||||
this.Controls.Add(this.tableLayoutPanel2);
|
||||
this.Controls.Add(this.tableLayoutPanel1);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
|
||||
this.Name = "SSConnectionForm";
|
||||
this.Text = "Secret Server API Login Data";
|
||||
this.Activated += new System.EventHandler(this.SSConnectionForm_Activated);
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
this.tableLayoutPanel1.PerformLayout();
|
||||
this.tableLayoutPanel2.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Label label3;
|
||||
|
||||
public System.Windows.Forms.TextBox tbSSURL;
|
||||
public System.Windows.Forms.TextBox tbUsername;
|
||||
public System.Windows.Forms.TextBox tbPassword;
|
||||
private System.Windows.Forms.Button btnOK;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||
public System.Windows.Forms.CheckBox cbUseSSO;
|
||||
private System.Windows.Forms.Label label4;
|
||||
private Label label5;
|
||||
private Label label6;
|
||||
public TextBox tbOTP;
|
||||
}
|
||||
}
|
||||
35
ExternalConnectors/DSS/SSConnectionForm.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace ExternalConnectors.DSS
|
||||
{
|
||||
public partial class SSConnectionForm : Form
|
||||
{
|
||||
public SSConnectionForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SSConnectionForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
SetVisibility();
|
||||
if (cbUseSSO.Checked)
|
||||
btnOK.Focus();
|
||||
else
|
||||
{
|
||||
if (tbPassword.Text.Length == 0)
|
||||
tbPassword.Focus();
|
||||
else
|
||||
tbOTP.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
SetVisibility();
|
||||
}
|
||||
private void SetVisibility()
|
||||
{
|
||||
bool ch = cbUseSSO.Checked;
|
||||
tbPassword.Enabled = !ch;
|
||||
tbUsername.Enabled = !ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
615
ExternalConnectors/DSS/SSConnectionForm.resx
Normal file
@@ -0,0 +1,615 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAQAQEAAAAEAIAAoQAAARgAAACAgAAABACAAKBAAAG5AAAAYGAAAAQAgACgJAACWUAAAEBAAAAEA
|
||||
IAAoBAAAvlkAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/f3e/f397vz9
|
||||
/e79/f3u7ezr7p6Xju5XTEHuNScV7i8hCe4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
|
||||
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
|
||||
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
|
||||
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHAjuKx8I7jYoFu5XTUHumJGO7u3r6+79/f3u/f397v39
|
||||
/e79/f3e/f399P//////////u7Wy/0M1KP8jEwD/KRgA/ywdBv8tHwj/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8sHAT/KRcA/yAR
|
||||
AP9DNSX/ubOt/////////////f399Pv8/e7/////m5aK/xoMAP8lFAD/LSAI/y0gCv8uIAr/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cv8uIAr/LB4J/ysfCf8uIQj/JBYA/xoLAP+elYz///////z9/O79/f3uvLWw/xsNAP8rHAX/LiAK/y0f
|
||||
Cf8tHwj/LR8I/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCf8tHwn/LR8J/y4gCv8pGgP/GgsA/7y3sP/9/f3u6unp7kM0
|
||||
I/8nFwH/LiEL/yweCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAr/LR8J/yIU
|
||||
AP9DNSL/6+nq7pqUju4fEQD/LiAI/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8uIAf/HxAA/5qTju5YTT7uJRYA/ywgCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/Kx8I/ygXAP9YTkDuOCkV7iweBv8sHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHQX/NykW7ioc
|
||||
CO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8uIAn/Lh8I/yocCO4qHAjuLyEK/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8K/y4gCf8rHAjuKx8I7i4gCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8J/y0fCP8tHwn/LR8J/y0f
|
||||
CP8tHwn/LR8J/y0fCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
CP8tHwj/LR8J/y0fCf8tHwj/LR8J/y0fCf8tHwn/LR8J/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8uIAj/Kx8I7isfCO4uIAn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8I/y8fCP8uHwj/LR8G/yweCP8uHgn/LR4H/yweB/8rHgf/Kx4H/yweCP8sHgj/Kx4I/yse
|
||||
CP8sHgj/LB4I/yweCP8sHgn/LB4J/yoeCP8qHgj/LB4I/y4fCP8sIQj/LB8H/y0eCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysf
|
||||
CO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tIAn/LiAK/y0WAf8sFgH/LRsG/ywbBv8uGwX/MR0B/y8cAv8uGwX/LRkG/ysZ
|
||||
Bf8qGQX/KhkF/ygaBP8oGgX/JxoF/ycaBf8pGgX/KRkG/ykZBv8nGQT/JxkF/ygZCv8jFAb/IBUG/ywe
|
||||
Cf8tHwf/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/Lh0I/y0XA/84Qyf/P31P/0aSXP9KkVv/UpJT/2OV
|
||||
Sv93mUf/jJ9A/6CjPP+vpjv/rac4/6ilN/+ppDn/qaQ4/62jOP+uozj/sKI2/7KiNv+yojX/s6Az/7Wi
|
||||
NP+0oDP/mIUs/1pIFv8lFQb/Kh4I/y4gCf8sHgn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR4I/ygWA/83b0X/SsOE/0zC
|
||||
f/9QvHf/Ubp0/1e4b/9YtGz/WrJl/2KwZP96tWD/lL9X/7fNUf/b2E//3NhP/9nWTP/X00v/29NL/9vS
|
||||
Sv/d0Ur/39FJ/9/RSP/j0Ej/5tBD/+3VRP/r0EX/jHYl/yMUBv8sHwb/LB4I/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ysV
|
||||
AP8zWTb/ScmH/0e8ef9Ltnb/TrRx/1Cyb/9VsGr/Wa1m/1mrYf9dqV3/XqRc/1yfWf9ooVL/g6tQ/7XD
|
||||
Tf/R0Ur/ys1H/87LR//Oykb/0MlG/9PKRf/Uy0X/18pF/9jJQf/byEH/4MtD/+7VRv9rWxz/HxMC/y8h
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/Kx8I/y0cBv8vHQj/QKFs/0fEgP9IuHX/TLZ0/0+0cP9UsW3/Vq9o/1isZP9cqWD/X6hc/2Ok
|
||||
Wf9loVX/aKBT/2iZUP9um0j/obZJ/87RTP/SzUz/yspI/8zLSP/Qykj/0stH/9XJR//VyUT/2clE/9fH
|
||||
RP/izkn/vao2/ywdBv8rHAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tGwX/MCoV/0azfP9Fvnv/Sbh2/0y2c/9PtG//VbFs/1av
|
||||
Z/9YrGT/XKlg/2KnXP9kpVr/ZaJX/2mhU/9unkz/bZtK/2qVR/+asUj/zdJM/8nOSf/JzUv/zcxJ/8/L
|
||||
R//Rykf/0cpE/9XKRP/WykX/3c5G/9DBQv86Kw//KRoH/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwr/LRoE/ywsGP9Dt3//R7x6/0q3
|
||||
dv9OtHL/ULNu/1Wwa/9Xrmb/Watj/12pXv9hplv/ZaRY/2eiVP9qoFH/bp5M/3OcSf9ymUb/b5BC/6m5
|
||||
SP/L0k7/yM5M/8vNS//LzEn/z8xJ/87LR//Rykf/08tH/9nNSP/RxkT/PS8R/ygZBv8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4g
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LB8K/y4a
|
||||
A/8uLRj/RLZ9/0e8ev9LtnX/ULNy/1Gybf9VsGr/Wa5l/1mrYv9dqVz/YKZa/2WkVv9oolL/aqBQ/2+e
|
||||
S/9zm0n/dJpE/3SUQ/+Dmj7/wMxM/8fPTP/IzUz/yc5L/8zNS//LzEr/z8tK/8/LSP/UzUj/0MZG/z0u
|
||||
EP8oGgX/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8uIAj/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/ywfCv8uGgP/Li0Y/0W2fP9Iu3n/TLZ0/1Czcf9Rsm3/VbBq/1muZf9aq2L/Xqlc/2Gm
|
||||
Wv9lpFb/aKJS/2ugUP9wnkv/c5tI/3WaQ/95l0H/do07/6m6Rf/F007/xM5N/8XOTP/IzUz/yM5L/8zN
|
||||
S//MzEn/0c9J/83HR/89LhD/KBoG/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwr/LRsE/zAsGP9HtHr/S7p3/061cv9Qs2//U7Fr/1Wv
|
||||
af9arGP/XKlh/2GnW/9lpFn/ZaNV/2igUf9unk7/cpxK/3WbRv91mEH/e5Y//3mPOP+YrEH/wtVT/7/O
|
||||
Uf/CzlD/xc1Q/8TOTP/IzUz/yc5L/87QTP/HyEn/PS8P/ykZBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8K/y0bBP8wLBj/R7R6/0u6
|
||||
d/9OtXL/ULNv/1Oxa/9Vr2n/Wqxj/1ypYf9hp1v/ZaRZ/2WjVf9ooFH/bp5O/3KcSv91m0b/dZhB/3yW
|
||||
P/97kDj/j6I9/7/VV/+90VH/wNBR/8PPUf/Bz0z/xM5M/8XOS//K0Uz/xclJ/z0vD/8pGQb/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isf
|
||||
CO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tGwT/MCwa/0qzef9MuXb/TrRx/1Kybv9Tr2n/V61k/12qYP9fqF3/YKZZ/2SkVv9qoVL/bKBP/26d
|
||||
TP9ym0b/dJpE/3iWQf98lD7/fY43/42gPf+111v/t9RU/7nSU/+/z1L/vtBQ/8HPUP/Cz03/xtFN/8HJ
|
||||
TP88MA//KhsF/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LRsE/zAsGv9Ks3n/TLl2/060cf9Ssm7/U69p/1etZP9dqmD/X6hd/2Cm
|
||||
Wf9kpFb/aqFS/2ygT/9unUz/cptG/3SaRP94lkH/fJQ+/36NNv+LpkP/qNxh/6zYWv+v1Ff/ttFV/7zS
|
||||
Uf/A0VL/wNFP/8TUT//Ayk3/PDAP/yobBP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4cA/8wLBn/SrN2/024dP9Ps3D/VLFs/1eu
|
||||
Z/9Zq2T/XKlf/2KnXf9jpVj/ZaNV/2qgUf9unk7/cJ1K/3KbRf92mUP/e5U+/3mUPP9/jDP/ibRM/5zi
|
||||
aP+g2mD/pdhe/6zVW/+01Ff/utNU/7vSUv/D1VH/vctN/zswEf8pGgb/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8uHQL/MCwY/0qz
|
||||
df9NuHP/T7Nv/1Wwa/9arWb/Wqtj/1qpX/9jplz/ZKRY/2WiVf9qoFH/b55N/3CdSf9ym0T/d5hC/3yW
|
||||
Pf98kjn/g441/4nRZP+N4mv/ld5n/5rcZf+g22D/p9hd/63XWf+y1Fb/utZU/7jMTv86MBH/KBoG/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysf
|
||||
CO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/Lh0D/zAsF/9LsnT/Trdz/1Gybv9WsGr/WK1l/1uqYv9dqV3/YqZb/2WkVv9nolT/aqBQ/2+e
|
||||
TP9ynEn/c5pD/3aXQf97lT3/gokz/3uvT/986nr/g+Rw/4nibf+O32v/lN5n/5rcY/+h22D/ptdd/6/Y
|
||||
Wv+xzlX/Oi8R/ygZBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4cBf8wLRb/TbFz/1C2cv9TsWz/V7Bo/1etZP9cqmH/Yahb/2Gl
|
||||
Wv9mo1T/aKFS/2ugTv9wnUr/dJpI/3WZQ/98kz3/gYo3/3WsUP9s7ID/but8/3fneP995XT/geJy/4fh
|
||||
bf+O32r/lN5m/5rbZP+j3GL/pdJd/zkvEv8pFwX/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHAX/MC0W/02xc/9QtnL/U7Fs/1ev
|
||||
aP9YrWT/XKpg/1+oW/9hpVr/ZqNV/2mhUv9sn03/cJxJ/3WVRP95kT3/eJZA/2TAYf9Y8Yj/XPOI/2fs
|
||||
gv9r637/cel7/3bneP995XT/g+Nw/4nibP+P32r/l+Bo/5jXYf85MBL/KhgG/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/KhsE/zAs
|
||||
FP9NsXH/UbVv/1awav9arWf/Wqxi/12oXv9jo1v/Y6FU/2mcUP9tm0r/bptI/2+cSv9rp1P/YMNo/1Do
|
||||
hf9G/ZX/SfaO/1PyjP9b8Ir/X+6F/2btgv9s637/cul6/3fmeP995XT/g+Nw/4vlbv+O22n/NjAU/yca
|
||||
BP8uHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/yobBP8wLBT/TbJy/1G1b/9WsGr/Wq1n/1uoX/9dp13/YKlf/2CvXv9gt2X/XcBq/1XQ
|
||||
dv9P4YP/SPCO/0T3lP9J85L/R/SR/0n1kf9L9ZD/TfSP/1Tzi/9b8Yj/Yu+E/2ftgP9r637/cel6/3jn
|
||||
dv+A6XT/geBs/zgxFf8pGwX/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y8fCf8rHAL/LisU/02ucv9Vs23/VLBp/1ipYP9XuGr/TtOA/0fg
|
||||
if9H5Ir/RuqO/0Xukf9G7pL/Ru2Q/0Ttjf9J7I7/R+6N/0nwj/9L8ZD/S/KR/0nzkP9I85D/TPSN/1X0
|
||||
if9d8If/Ye6D/2fsf/9s6nz/cux7/3Xic/81MRb/KxkE/ywfCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwn/Lh0D/y4gCP9PnWP/Vbdx/1eq
|
||||
ZP9TwXP/RuSM/0fmjv9F4ov/Q+SJ/0bliv9F5or/RuaM/0bojP9G6or/R+uM/0jtjP9J7o3/SO+O/0nw
|
||||
j/9I8o//RvKP/0f0j/9K947/S/WN/1Tyif9b8YT/YO6D/2n2hP9kz27/KyEM/y4dBv8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LB8J/y4e
|
||||
B/8sFgH/PGU2/1i9dP9Utm3/RN6J/0Leif9J3on/SN+K/0Xgif9H4ov/R+KL/0fjjP9I5Yz/R+eL/0jo
|
||||
jP9I6o3/SeuO/0jsjf9J7Y7/SO+O/0jwjv9J8Y//S/OQ/0r0kf9M9ZD/T/SO/1Pziv9h/5X/SIJE/yoP
|
||||
Af8sHwf/LR8K/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwn/LBwH/ywdCf9IfEf/Tsx8/0Ddif9F14X/RduH/0bdiP9E3on/Rd+K/0fl
|
||||
jf9I6JD/SOqQ/0jsj/9J7ZD/SO6R/0nvkv9H8JD/SPKR/0n0kf9K84//SPCP/0fxkP9J8pH/SvWS/0r3
|
||||
lP9M/pn/RKxh/y0YBv8sHAf/LR8J/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sGgX/KhUE/0GsbP9E4Y7/QteG/0TZ
|
||||
h/9F2oj/RdyI/0Teiv9Ez3//RMp7/0XNe/9Eznr/RM97/0TQff9F0H3/Q9J7/0PTfP9E0n3/SNyC/0zq
|
||||
iv9M6Yr/TeqL/0zsiv9P8Iz/S7Vp/ywWB/8sGgT/LiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwn/Lh8G/ywR
|
||||
BP87m2b/ReCR/0PUiP9E1oj/RtiJ/0bZh/9D5o//Nlk1/ywZB/8vJg3/LyYM/y8mDP8vJQ3/LyUN/ywm
|
||||
DP8sJgz/KhgH/z9sO/9c2Hr/Vslx/1fJcf9YyXL/XdF3/0yRUv8pEwT/LyEJ/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isf
|
||||
CO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LB8J/y0eBv8sEwX/PJ1n/0Tej/9D0of/Q9SG/0TWiP9F2Ib/QuWO/zVPLv8sDgD/MBsI/y8b
|
||||
B/8vGwb/LhsE/y4bBP8sGgb/KxoG/ywKAP87Zjr/U+CD/1HQe/9R0Hv/UtB7/1Pcg/9En17/KxMD/y4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4fCf8sHwj/LhUD/0CbZP9F2o7/RM+H/0TQhv9F0of/Q9WG/0bk
|
||||
kf8yVzL/KxEA/y4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tDgD/O3ZF/0zslP9I2ob/SdqG/0na
|
||||
hv9L5o7/QaNm/ywVAv8uHgf/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwj/LiAJ/ywUAf89jlf/Q9qO/0LN
|
||||
hf9Dz4T/RNCG/0LRg/9H4I//O4dQ/yoOAP8tIAj/LR8J/y0fCf8tHwn/LR8J/y0fCP8tHgj/LBMC/z+n
|
||||
ZP9H6pH/RN+K/0Tfiv9E34r/RuyT/z2bXv8uEwH/LR8H/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LB8J/y0f
|
||||
Cf8rDwD/NXNF/0PZjP9DyYX/RcuF/0XNhv9EzYT/RdSJ/0TFfv8uKRH/LBYC/ywfCP8tHwn/LR8J/ywf
|
||||
Cf8sHwj/KBAA/zZAIf9H34n/R+GL/0Xdif9F3Yn/Rd2I/0nvlv86gU3/Kg4A/y0eCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysf
|
||||
CO4rHwjuLiAI/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCf8rHwj/LRMB/zZRLv9H0ov/QsmE/0LKhP9Ey4X/Q8uE/0XMhP9H3I7/Pppj/ysa
|
||||
Bv8sEQD/Lh0I/y0fB/8tGgX/Kg4A/y0lD/9AuXH/R+qQ/0Lci/9D34n/Rd6J/0PdiP9C65H/M1wx/y8U
|
||||
Av8uIAf/LR8J/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y4gCP8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LB4I/y4bBv8uJQ//QLV4/0LNh/9Bx4P/Q8iE/0PJ
|
||||
g/9Ey4P/RMuG/0Xajv9EpGn/MEoo/ysfC/8uFgr/LiUN/zNULf8+vHf/SOWQ/0TYh/9F3Ij/Rt6J/0fe
|
||||
i/9E44z/Rc9//y0qDv8vGQX/LSAH/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCf8tHwn/KxEA/zt2
|
||||
Sv9F047/QcOC/0PFg/9Cx4L/RMmD/0PKgv9EzIP/RdaL/0fUif8/t3P/Pqpv/0C+dv9H3I//Rt6O/0HW
|
||||
hv9E1oj/RdiI/0Tah/9G3In/R+6U/zuET/8sDwD/LR8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysfCO4rHwjuLiAJ/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0aBP8wKhH/QLV4/0HKhv9BxIP/QsWB/0LHgv9CyIL/QsmD/0XKg/9FzoT/Q9KH/0LY
|
||||
iP9G14r/Q9CH/0XRhf9F0oX/RNSI/0LXiP9C14X/ReGM/0XOgv8uKxT/KxgF/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwf/LRYA/zRTL/9Dy4v/QcWG/0HCgf9CxIL/QsSC/0PE
|
||||
g/9CxoP/Q8iC/0TKhP9Dy4T/RsyF/0PNhf9Cz4b/RNCH/0XSiP9H0oj/QtiJ/0jikP8yWjX/KxAA/ywg
|
||||
B/8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LSAH/y8gBv8vEgD/OWlC/0PL
|
||||
if8+xYP/P8KA/0DCgf9Bw4L/QsaC/0LHgf9DyIL/QsqC/0PLg/9CzIT/Q82F/0TOhf9Fz4X/RdaK/0fg
|
||||
lf85dkf/KhEA/yseB/8uHwj/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8vHwn/Kh4I/y8WAP82Yzj/P7+C/0LLiP9CwoL/QMGC/0HFhP9BxIH/Q8WD/0PGhP9Ex4X/RMmE/0bK
|
||||
hf9DzYT/QtyN/0nTiP86aj3/LRIA/y4eCP8sIAr/Lh8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8rIAj/KhQA/zU/IP9Bl2X/RseI/0LLiv8/x4b/QMaD/0DF
|
||||
g/9CxoT/Q8mG/0LOh/9G1o3/RtSK/0WjaP8yQiD/LBEA/y8eCP8sIAn/LR8I/y0fCP8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwn/LR4I/y4gCv8sFQD/KxoE/zRJ
|
||||
Kf9Cg1P/Q6tv/0O6ev9EwoL/QsOC/0a+fv9FsHD/QoZS/zZJJ/8sGgT/KxMB/y4fCf8wIAj/Lh8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAI/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8J/y0f
|
||||
Cf8tHwj/LR8J/y0dCP8rEwL/LBQA/zEjCf8yLxP/Njce/zQ3H/8yMBT/MiMJ/y8UAP8rEgD/LB0H/y4g
|
||||
Cf8tHwn/LB8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y4gCP8rHwjuKx8I7i4gCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAI/yseB/8rHQj/LBoF/ysXAv8sGAP/KxsF/ygb
|
||||
Bv8wHgf/LR8J/y4fCv8uIAr/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isf
|
||||
CO4uIAj/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8I/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LB8J/y0fCf8tHwj/LR8I/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LiAJ/ysfCO4rHwjuLyEJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuLh8J7i8hCP8tIAj/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwj/LyEJ7jgqFu4sHgX/LR8H/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LB4G/zUn
|
||||
FO5YTT7uJhUA/y4gCv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LSAL/yoZAP9XTD7um5SO7iMRAP8vIAn/LR8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwn/LR8I/ywfBv8iEgD/npeO7urq6e5ENSX/JBQA/y0hCP8tIAf/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/Kx0H/y4gCv8lFQD/QzYm/+vr6e79/f3uvLax/xoM
|
||||
AP8pGwL/LiEI/y0fCf8tHwj/LR8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
|
||||
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHQX/Gw0A/723
|
||||
tP/9/f3u+vv87v////+ak4n/GQsA/yQUAP8uHwn/LR8J/y0fCP8uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCf8sHgj/LB8J/y4g
|
||||
B/8mFwD/Gw0A/5qViv//////+/z87v39/fT//////////7q1sP9DNCb/IxEA/ycWAP8rHgX/LiAH/y8h
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/zAh
|
||||
Cv8uHwj/LB0G/yQWAP8fEQD/QzQm/7q0r/////////////39/fT9/f3e/f397vz9/e79/f3u6+rp7puU
|
||||
ju5YTj/uOCkW7i4fCu4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
|
||||
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
|
||||
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
|
||||
CO4rHwjuKx8I7isfCO4rHwjuKyAI7jcpFu5YTj7umpOO7uvr6u79/f3u/Pz97v39/e79/f3eKAAAACAA
|
||||
AABAAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP39/dzt6+vtdGte7TQmFu0rHQjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7SsdBu00Jhbtc2le7e3r6e39/f3c6ubl81ZKOP8dDgD/LB4G/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LyAK/ywdBf8dDgD/VUk4/+rn5vN0a13tHg4A/y4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8dDQD/dWtd7TQm
|
||||
Eu0rHQb/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ywd
|
||||
Bf80JhLtKx0G7S8gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LiAJ/ysdBu0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwj/LBcC/ysT
|
||||
AP8pFAH/JhMC/yISA/8hEQP/IBID/yASA/8gEgT/HxIE/x8RBP8jFgb/LB4I/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR4I/ywY
|
||||
BP81QiP/Olw1/0RbLP9WXSb/bGMj/3RnIP9vZCD/bmIg/3FiIP9yYh7/dWMf/1ZFFv8lFwb/LB4I/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8rFgL/N2c//0vDgf9SwHr/W7pv/2a2Zf9+t13/p8RT/9bYT//g20z/3tZL/+LWSv/p10f/69RF/39r
|
||||
Iv8iFQT/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LBwG/y4lD/9Esnb/Sr55/1Kybv9XrWf/Xahe/1+iWP9imlH/faJJ/8DHSv/Pz0r/z8tI/9PK
|
||||
Rf/f0Uf/z75A/zQmC/8pGwj/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8sGgX/Ly0W/0S0ef9NuHb/U7Fs/1itZP9ep1z/ZqNW/2yfTv9slUb/hqFE/8fP
|
||||
TP/Lzkv/zcxJ/9TOSf/Ow0T/PS4P/ygaB/8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/ywbBf8wLBX/RrF1/0+4dP9UsGv/Wqxj/2CmWv9molT/b55N/3WZ
|
||||
RP93kTz/r8BI/8fST//Hzkz/zdFM/8bERv87LQ7/KRoH/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0f
|
||||
CO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LBsF/zAsFf9Jr3T/ULdz/1WvaP9dqmD/YqVZ/2mg
|
||||
Uv9wnEv/dplD/3mOOf+iuEn/wNZV/8HPT//H0k7/v8ZJ/zstDv8pGgf/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sGwX/MCwV/0mvc/9RtnH/Vq1m/16p
|
||||
Xv9jpFf/bKBQ/3CcSP93mEH/fIw2/5bCU/+q3F//tdNW/8HWUv+7yEv/Oi4O/ykaB/8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ywbBf8wLRT/S65w/1O1
|
||||
bv9ZrGT/YKdc/2ajVv9sn0//cpxG/3uPOf+AmT7/hd5t/5Hhav+e22L/rdtc/6/KUf85LQ//KRoH/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LBsG/zAs
|
||||
E/9NrW7/VrRt/1qrYf9ipFj/aZ5R/3GXR/94kj//dKFI/2bddv9t74H/eeZ2/4Xib/+U4mr/mdFe/zgt
|
||||
EP8qGQb/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8sGwb/LiwU/06ubf9ZsGn/Xahf/2CqXv9jr13/YLtj/1jRdP9K8o7/SvqT/1byi/9j7oP/b+p8/3zq
|
||||
d/+D22r/NjAS/yoaBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/ywcBv8vJw7/Ualp/1e3bP9N0n//R+GJ/0bojf9E75H/RPKR/0jwj/9J8ZD/R/OR/0z1
|
||||
jv9W8on/ZfWF/2vhd/8yKg//KxoG/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8I/ysXAv9CaTr/TNSC/0PijP9F34n/RueP/0n0lv9J95b/SvqY/0n9
|
||||
mP9J+5b/R/OR/0j4lP9Q/pb/RI5N/ysTAv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0f
|
||||
CO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LRwG/ywbB/9Aunf/RN6M/0bijf9At3D/OXJC/zp7
|
||||
Rf86fEb/NnhD/z+SUv9S3H//U+KD/068bf8sGgj/LRsH/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHQf/LRwI/0CydP9E2oz/RuWR/zqO
|
||||
Vv8qAAD/LA8A/ywOAP8pAgD/Mzkb/0/bhP9Q24T/SLRs/y0dCP8tHQf/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCP8tGQT/PqZp/0TW
|
||||
jP9F1or/Qbh0/ywbBv8sFQL/LBwG/ykGAP86eEX/Ru6U/0Xpkf9At3H/LRkE/y0eB/8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ysR
|
||||
AP86fk//RNeP/0TJg/9G14z/PI1Y/y4pEP8tHQn/M1ky/0TWhv9F4Yz/R/CV/zqOU/8sEAD/LR8I/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LBcD/zE5HP9CxoX/QsqF/0PIgv9F1ov/QsR+/0C7d/9F2oz/RNmK/0Xdiv9G34z/MT0d/ywW
|
||||
A/8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LBQA/zdmPf9C0Iz/QcmG/0LFgv9DzIX/RNGI/0POhv9F14v/R+WU/zdv
|
||||
Qv8qEQD/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHgj/LBUB/zVeNv9Ct3v/Q8+M/0LRjP9E1I7/RteO/0TE
|
||||
fv84ZDr/LRQB/y0eCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHgj/LBMA/y8qEf84Xjb/O3lM/zt6
|
||||
Tf86YDf/MCoR/ysSAP8tHgj/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0f
|
||||
CO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LBoF/ysT
|
||||
AP8sEQD/KxEA/ysSAP8sGgX/LSAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8uIAn/LR8I7SweBu0vIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8rHQbtNCYT7SwdBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LB4G/zQmEu11a17tHQ4A/y4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0gCf8dDgD/dmxe7ern5vNVSTf/Hg4A/ywdBv8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8rHQX/Hg4A/1ZKOP/q5+fz/f393Ozr6e10aV7tNSYU7Swe
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLB4I7TQmFO1zaV7t7evp7f39/dwoAAAAGAAAADAA
|
||||
AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f393JSPhO00JhbtKx0G7S0fCO0tHwjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0rHQbtMyUW7ZSO
|
||||
g+39/f3ckouC8yESAP8qHAT/LyAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8vIAn/KhwE/yERAP+Si4LzMyUR7SocBP8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/yocBP8zJRLtKx0F7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCP8sGgX/KxoG/yka
|
||||
B/8oGgf/KBoH/ygaB/8nGgf/KBoH/yweCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHQXtLR8I7S4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LRwH/y0aBf8wLRP/Ni8Q/0ExDv9FMw3/PjAN/z4vDv8/MA3/PS4N/ycZ
|
||||
B/8qHAj/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHQf/LB8K/z6E
|
||||
VP9NsnL/Wapi/3GoVv+bskv/x8VG/87FRP/PwkL/2cZB/6GMLf8tHwj/Kx0I/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8sFgH/MkYn/0nIhP9Rt3L/WK9m/16nXf9jnlP/iKlK/8nO
|
||||
S//T0Er/2M9H/+jYSv9XSRf/IxUF/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
|
||||
Cf8sFQH/M0os/0vBfv9Tsm3/Wqxi/2OlWP9sn07/bJJD/5uuRP/M0k3/y81L/9nWTP9cTxv/IhQF/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8rFQH/NEkq/02+e/9UsGr/Xalg/2Wj
|
||||
Vv9unkz/dJRB/4ufPv+/01P/xM9O/8/XT/9YThr/IxQE/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
|
||||
Cf8tHwn/LR8J/y0fCf8rFgH/NUkp/068ef9Xrmf/X6dc/2iiVP9wnUn/eI86/4ilRP+k3mL/tNVX/8ba
|
||||
U/9WTxv/JBQE/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8rFgH/Nkgo/1K7
|
||||
dv9ZrWT/YqRZ/2ycTf93kj//epU+/3bVa/+D53T/lN5n/63hYv9RTx//JRMD/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8qFQL/NUop/1a6c/9cqWH/Yatd/2SwW/9gv2b/UeeE/1L4
|
||||
kP9k7oL/dOh5/4vvdf9JViX/JxIC/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
|
||||
Cf8rFwP/NT4e/1a5cP9N0H3/R+GI/0fwkv9F+5n/SPuY/0j7lv9J95H/VfaM/2f6iP88SyH/KhQC/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHgj/LBUC/z6ETv9E6JL/Rd+L/0LG
|
||||
ef9DwHP/QsR1/0LHdP9L4oX/T/aS/0KaV/8rFgT/LR4I/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/Kw8A/zVgOv9F4pL/Rd+N/zJGJP8tEAD/LRoF/ywaBv9Lum3/VOeL/zle
|
||||
M/8qDwD/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/KxMA/zVX
|
||||
Mf9F2I7/RtuO/zl3R/8qBQD/KQQA/zJDIv9F4Iv/RvCW/zVfNf8rEQD/LR8J/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LBoF/zAtE/9Cv3//RNCJ/0TNhf86g1D/N29C/0LF
|
||||
fP9G6JH/RNeF/y8wFP8sGQT/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/ywTAP82Yzv/QtKN/0PPif9F2I3/Rd2Q/0XekP9H5ZP/N2w//ysRAP8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCP8sFQH/NVs1/0Cu
|
||||
dP9CxoT/RMqG/0O3dv83YTj/LBQB/y0eCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHgj/LBMA/y4iC/8zPB7/Mj0e/y8hCv8rEgD/LR4I/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtKx0F7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/ywcB/8sFwP/LBcD/ywcB/8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
|
||||
Cf8rHQbtNCUS7SocBP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
|
||||
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/yocBP80JhLtkouD8yERAP8qHAT/LyAJ/y4g
|
||||
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
|
||||
Cf8vIAn/KhwE/yERAP+VjITz/f393JSOhO00JhbtKx0G7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0rHQbtMyUW7ZSOg+39/f3cKAAAABAA
|
||||
AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL65stw/MR/tKBkF7S0fCO0tHwjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0oGQXtPzEf7b65stw8MR3zJxgA/y8gCf8uIAn/LiAJ/y4g
|
||||
Cf8uHwn/Lh8J/y4fCf8uHwn/LiAJ/y4gCf8uIAn/LyAJ/ycYAP88MR3zKBoE7S8gCf8tHwn/LSAJ/ywa
|
||||
Bf8rFAH/LBcD/ysXBP8kFQT/IxUF/yATBP8oGgf/LiAJ/y0fCf8vIQn/KBkE7S0fCO0uIAn/LR8J/ywa
|
||||
Bf8wMxj/QYNR/1OFSP94iTr/npg0/6aYM/+hji7/RDQP/ycaB/8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0f
|
||||
Cf8rEQD/OW9F/1LNhP9dtGj/aahY/5u2TP/Z2U3/7+ZP/4V2KP8eEAT/LR8J/y4gCf8tHwjtLR8I7S4g
|
||||
Cf8tHwn/KxIA/ztvRf9Tv3f/Xqhd/2ufT/91kj7/tMNK/9fgU/+AeSr/IBAD/y0fCf8uIAn/LR8I7S0f
|
||||
CO0uIAn/LR8J/ysTAP89bUL/Vrxz/2OjWP9ylkX/fZQ7/5XTX/+952D/d3ot/yEQA/8tHwn/LiAJ/y0f
|
||||
CO0tHwjtLiAJ/y0fCf8qEwD/P29C/1y6bv9iqlv/Z7Fa/1zad/9l8oX/ifZ6/2OGPP8lDgH/LR8J/y4g
|
||||
Cf8tHwjtLR8I7S4gCf8tHwn/KhQB/zxULf9P0oD/SOSL/0b1lf9F/Jn/SPaS/1j7kP9DbTb/KRAB/y0f
|
||||
Cf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0cB/8uIwz/QcuC/0HCef8zRSL/MD0e/0SmYP9M0n7/LSMN/ywc
|
||||
Bv8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHQj/LRsG/0Cxc/9Ez4b/MDca/yweCf9Cxnr/Qsd6/y0b
|
||||
Bv8tHQj/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/ywUAf81XTf/RNiR/0TTiv9E1In/R+2Y/zZn
|
||||
Ov8rEgH/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHgj/LBcC/zRWMv8+nWb/P6Jo/zZb
|
||||
NP8sFgL/LR4I/y0fCf8tHwn/LiAJ/y0fCO0oGgTtLyEJ/y0fCf8tHwn/LR8J/y0fCP8sFAH/LBYC/ywW
|
||||
Av8rEwD/LR4I/y0fCf8tHwn/LR8J/y8hCf8oGQTtPTEd8ycYAP8vIAn/LiAJ/y4gCf8uIAn/LiAJ/y4f
|
||||
CP8uHwn/LiAJ/y4gCf8uIAn/LiAJ/y8gCf8nGAD/PTEd87u5stw/MR/tKBkF7S0fCO0tHwjtLR8I7S0f
|
||||
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0oGQXtPzEf7b65stwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
377
ExternalConnectors/DSS/SecretServerAuthentication.cs
Normal file
@@ -0,0 +1,377 @@
|
||||
//----------------------
|
||||
// <auto-generated>
|
||||
// Generated using the NSwag toolchain v13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org)
|
||||
// </auto-generated>
|
||||
//----------------------
|
||||
|
||||
#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
|
||||
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
|
||||
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
|
||||
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
|
||||
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
|
||||
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
|
||||
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
|
||||
|
||||
namespace SecretServerAuthentication.DSS
|
||||
{
|
||||
using System = global::System;
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
|
||||
public partial class OAuth2ServiceClient
|
||||
{
|
||||
private string _baseUrl = "";
|
||||
private System.Net.Http.HttpClient _httpClient;
|
||||
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
|
||||
|
||||
public OAuth2ServiceClient(string baseUrl, System.Net.Http.HttpClient httpClient)
|
||||
{
|
||||
BaseUrl = baseUrl;
|
||||
_httpClient = httpClient;
|
||||
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
|
||||
}
|
||||
|
||||
private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
|
||||
{
|
||||
var settings = new Newtonsoft.Json.JsonSerializerSettings();
|
||||
UpdateJsonSerializerSettings(settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
public string BaseUrl
|
||||
{
|
||||
get { return _baseUrl; }
|
||||
set { _baseUrl = value; }
|
||||
}
|
||||
|
||||
protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } }
|
||||
|
||||
partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);
|
||||
|
||||
|
||||
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
|
||||
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
|
||||
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
|
||||
/// <summary>Retrieve or Refresh Access Token</summary>
|
||||
/// <param name="grant_type">Authentication grant type. Use 'password' when authenticating, and 'refresh_token' when refreshing a token.</param>
|
||||
/// <param name="username">Secret Server authentication username. Required when authenticating.</param>
|
||||
/// <param name="password">Secret Server authentication password. Required when authenticating.</param>
|
||||
/// <param name="refresh_token">The refresh token. Required when refreshing a token.</param>
|
||||
/// <returns>Successful retrieval of an access token</returns>
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, string OTP) => AuthorizeAsync(grant_type, username, password, refresh_token, System.Threading.CancellationToken.None, OTP);
|
||||
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <summary>Retrieve or Refresh Access Token</summary>
|
||||
/// <param name="grant_type">Authentication grant type. Use 'password' when authenticating, and 'refresh_token' when refreshing a token.</param>
|
||||
/// <param name="username">Secret Server authentication username. Required when authenticating.</param>
|
||||
/// <param name="password">Secret Server authentication password. Required when authenticating.</param>
|
||||
/// <param name="refresh_token">The refresh token. Required when refreshing a token.</param>
|
||||
/// <returns>Successful retrieval of an access token</returns>
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public async System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, System.Threading.CancellationToken cancellationToken, string OTP)
|
||||
{
|
||||
var urlBuilder_ = new System.Text.StringBuilder();
|
||||
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/oauth2/token");
|
||||
|
||||
var client_ = _httpClient;
|
||||
var disposeClient_ = false;
|
||||
try
|
||||
{
|
||||
using (var request_ = new System.Net.Http.HttpRequestMessage())
|
||||
{
|
||||
var keyValues_ = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string, string>>();
|
||||
if (grant_type == null)
|
||||
throw new System.ArgumentNullException("grant_type");
|
||||
else
|
||||
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("grant_type", ConvertToString(grant_type, System.Globalization.CultureInfo.InvariantCulture)));
|
||||
if (username != null)
|
||||
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("username", ConvertToString(username, System.Globalization.CultureInfo.InvariantCulture)));
|
||||
if (password != null)
|
||||
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("password", ConvertToString(password, System.Globalization.CultureInfo.InvariantCulture)));
|
||||
if (refresh_token != null)
|
||||
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("refresh_token", ConvertToString(refresh_token, System.Globalization.CultureInfo.InvariantCulture)));
|
||||
if (OTP != null)
|
||||
request_.Headers.Add("OTP", ConvertToString(OTP, System.Globalization.CultureInfo.InvariantCulture));
|
||||
|
||||
request_.Content = new System.Net.Http.FormUrlEncodedContent(keyValues_);
|
||||
request_.Method = new System.Net.Http.HttpMethod("POST");
|
||||
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||
|
||||
PrepareRequest(client_, request_, urlBuilder_);
|
||||
|
||||
var url_ = urlBuilder_.ToString();
|
||||
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
|
||||
|
||||
PrepareRequest(client_, request_, url_);
|
||||
|
||||
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
var disposeResponse_ = true;
|
||||
try
|
||||
{
|
||||
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
|
||||
if (response_.Content != null && response_.Content.Headers != null)
|
||||
{
|
||||
foreach (var item_ in response_.Content.Headers)
|
||||
headers_[item_.Key] = item_.Value;
|
||||
}
|
||||
|
||||
ProcessResponse(client_, response_);
|
||||
|
||||
var status_ = (int)response_.StatusCode;
|
||||
if (status_ == 200)
|
||||
{
|
||||
var objectResponse_ = await ReadObjectResponseAsync<TokenResponse>(response_, headers_, cancellationToken).ConfigureAwait(false);
|
||||
if (objectResponse_.Object == null)
|
||||
{
|
||||
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
|
||||
}
|
||||
|
||||
return objectResponse_.Object;
|
||||
}
|
||||
else
|
||||
if (status_ == 400)
|
||||
{
|
||||
var objectResponse_ = await ReadObjectResponseAsync<TokenErrorResponse>(response_, headers_, cancellationToken).ConfigureAwait(false);
|
||||
if (objectResponse_.Object == null)
|
||||
{
|
||||
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
|
||||
}
|
||||
throw new ApiException<TokenErrorResponse>("An error occurred", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (disposeResponse_)
|
||||
response_.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (disposeClient_)
|
||||
client_.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected struct ObjectResponseResult<T>
|
||||
{
|
||||
public ObjectResponseResult(T responseObject, string responseText)
|
||||
{
|
||||
this.Object = responseObject;
|
||||
this.Text = responseText;
|
||||
}
|
||||
|
||||
public T Object { get; }
|
||||
|
||||
public string Text { get; }
|
||||
}
|
||||
|
||||
public bool ReadResponseAsString { get; set; }
|
||||
|
||||
protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
if (response == null || response.Content == null)
|
||||
{
|
||||
return new ObjectResponseResult<T>(default(T), string.Empty);
|
||||
}
|
||||
|
||||
if (ReadResponseAsString)
|
||||
{
|
||||
var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText, JsonSerializerSettings);
|
||||
return new ObjectResponseResult<T>(typedBody, responseText);
|
||||
}
|
||||
catch (Newtonsoft.Json.JsonException exception)
|
||||
{
|
||||
var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
|
||||
throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
using (var streamReader = new System.IO.StreamReader(responseStream))
|
||||
using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
|
||||
{
|
||||
var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
|
||||
var typedBody = serializer.Deserialize<T>(jsonTextReader);
|
||||
return new ObjectResponseResult<T>(typedBody, string.Empty);
|
||||
}
|
||||
}
|
||||
catch (Newtonsoft.Json.JsonException exception)
|
||||
{
|
||||
var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
|
||||
throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value is System.Enum)
|
||||
{
|
||||
var name = System.Enum.GetName(value.GetType(), value);
|
||||
if (name != null)
|
||||
{
|
||||
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
|
||||
if (field != null)
|
||||
{
|
||||
var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
|
||||
as System.Runtime.Serialization.EnumMemberAttribute;
|
||||
if (attribute != null)
|
||||
{
|
||||
return attribute.Value != null ? attribute.Value : name;
|
||||
}
|
||||
}
|
||||
|
||||
var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
|
||||
return converted == null ? string.Empty : converted;
|
||||
}
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant();
|
||||
}
|
||||
else if (value is byte[])
|
||||
{
|
||||
return System.Convert.ToBase64String((byte[])value);
|
||||
}
|
||||
else if (value.GetType().IsArray)
|
||||
{
|
||||
var array = System.Linq.Enumerable.OfType<object>((System.Array)value);
|
||||
return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
|
||||
}
|
||||
|
||||
var result = System.Convert.ToString(value, cultureInfo);
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>API access token response</summary>
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
|
||||
public partial class TokenResponse
|
||||
{
|
||||
/// <summary>Authentication token</summary>
|
||||
[Newtonsoft.Json.JsonProperty("access_token", Required = Newtonsoft.Json.Required.Always)]
|
||||
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
|
||||
public string Access_token { get; set; }
|
||||
|
||||
/// <summary>Authentication token type</summary>
|
||||
[Newtonsoft.Json.JsonProperty("token_type", Required = Newtonsoft.Json.Required.Always)]
|
||||
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public TokenResponseToken_type Token_type { get; set; }
|
||||
|
||||
|
||||
private string _Expires_in;
|
||||
/// <summary>Authentication token expiration time, in seconds</summary>
|
||||
[Newtonsoft.Json.JsonProperty("expires_in", Required = Newtonsoft.Json.Required.Always)]
|
||||
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
|
||||
// public string Expires_in { get; set; }
|
||||
|
||||
public string Expires_in
|
||||
{
|
||||
get { return _Expires_in; }
|
||||
set
|
||||
{
|
||||
_Expires_in = value;
|
||||
Expires_on = DateTime.UtcNow.AddSeconds(Double.Parse(value) - 60);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Authentication token expiration time in UTC</summary>
|
||||
public DateTime Expires_on { get; set; }
|
||||
|
||||
/// <summary>Refresh token. This is only provided when the server is set to allow refresh tokens for web services and when the session timeout duration is not set to Unlimited.</summary>
|
||||
[Newtonsoft.Json.JsonProperty("refresh_token", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public string Refresh_token { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>API access token error response</summary>
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
|
||||
public partial class TokenErrorResponse
|
||||
{
|
||||
/// <summary>Authentication token</summary>
|
||||
[Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)]
|
||||
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
|
||||
public string Message { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Authentication grant type. Use 'password' when authenticating, and 'refresh_token' when refreshing a token.</summary>
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
|
||||
public enum Grant_type
|
||||
{
|
||||
[System.Runtime.Serialization.EnumMember(Value = @"password")]
|
||||
Password = 0,
|
||||
|
||||
[System.Runtime.Serialization.EnumMember(Value = @"refresh_token")]
|
||||
Refresh_token = 1,
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
|
||||
public enum TokenResponseToken_type
|
||||
{
|
||||
[System.Runtime.Serialization.EnumMember(Value = @"bearer")]
|
||||
Bearer = 0,
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
|
||||
public partial class ApiException : System.Exception
|
||||
{
|
||||
public int StatusCode { get; private set; }
|
||||
|
||||
public string Response { get; private set; }
|
||||
|
||||
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> Headers { get; private set; }
|
||||
|
||||
public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Exception innerException)
|
||||
: base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
Response = response;
|
||||
Headers = headers;
|
||||
}
|
||||
|
||||
public override string ToString() => string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
|
||||
public partial class ApiException<TResult> : ApiException
|
||||
{
|
||||
public TResult Result { get; private set; }
|
||||
|
||||
public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, TResult result, System.Exception innerException)
|
||||
: base(message, statusCode, response, headers, innerException)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma warning restore 1591
|
||||
#pragma warning restore 1573
|
||||
#pragma warning restore 472
|
||||
#pragma warning restore 114
|
||||
#pragma warning restore 108
|
||||
#pragma warning restore 3016
|
||||
335
ExternalConnectors/DSS/SecretServerInterface.cs
Normal file
@@ -0,0 +1,335 @@
|
||||
using Microsoft.Win32;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using Org.BouncyCastle.Security;
|
||||
using SecretServerAuthentication.DSS;
|
||||
using SecretServerRestClient.DSS;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace ExternalConnectors.DSS;
|
||||
|
||||
public class SecretServerInterface
|
||||
{
|
||||
private static class SSConnectionData
|
||||
{
|
||||
public static string ssUsername = "";
|
||||
public static string ssPassword = "";
|
||||
public static string ssUrl = "";
|
||||
public static string ssOTP = "";
|
||||
public static bool ssSSO = false;
|
||||
public static bool initdone = false;
|
||||
|
||||
//token
|
||||
public static string ssTokenBearer = "";
|
||||
public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
|
||||
public static string ssTokenRefresh = "";
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (initdone == true)
|
||||
return;
|
||||
|
||||
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
|
||||
try
|
||||
{
|
||||
// display gui and ask for data
|
||||
SSConnectionForm f = new();
|
||||
string? un = key.GetValue("Username") as string;
|
||||
f.tbUsername.Text = un ?? "";
|
||||
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
|
||||
|
||||
string? url = key.GetValue("URL") as string;
|
||||
if (url == null || !url.Contains("://"))
|
||||
url = "https://cred.domain.local/SecretServer";
|
||||
f.tbSSURL.Text = url;
|
||||
|
||||
var b = key.GetValue("SSO");
|
||||
if (b == null || (string)b != "True")
|
||||
ssSSO = false;
|
||||
else
|
||||
ssSSO = true;
|
||||
f.cbUseSSO.Checked = ssSSO;
|
||||
|
||||
// show dialog
|
||||
while (true)
|
||||
{
|
||||
_ = f.ShowDialog();
|
||||
|
||||
if (f.DialogResult != DialogResult.OK)
|
||||
return;
|
||||
|
||||
// store values to memory
|
||||
ssUsername = f.tbUsername.Text;
|
||||
ssPassword = f.tbPassword.Text;
|
||||
ssUrl = f.tbSSURL.Text;
|
||||
ssSSO = f.cbUseSSO.Checked;
|
||||
ssOTP = f.tbOTP.Text;
|
||||
// check connection first
|
||||
try
|
||||
{
|
||||
if (TestCredentials() == true)
|
||||
{
|
||||
initdone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
MessageBox.Show("Test Credentials failed - please check your credentials");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// write values to registry
|
||||
key.SetValue("Username", ssUsername);
|
||||
key.SetValue("URL", ssUrl);
|
||||
key.SetValue("SSO", ssSSO);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
key.Close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TestCredentials()
|
||||
{
|
||||
if (SSConnectionData.ssSSO)
|
||||
{
|
||||
// checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!String.IsNullOrEmpty(GetToken()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SecretsServiceClient ConstructSecretsServiceClient()
|
||||
{
|
||||
string baseURL = SSConnectionData.ssUrl;
|
||||
if (SSConnectionData.ssSSO)
|
||||
{
|
||||
// REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
|
||||
var handler = new HttpClientHandler() { UseDefaultCredentials = true };
|
||||
var httpClient = new HttpClient(handler);
|
||||
{
|
||||
// Call REST API:
|
||||
return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
{
|
||||
|
||||
var token = GetToken();
|
||||
// Set credentials (token):
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
// Call REST API:
|
||||
return new SecretsServiceClient($"{baseURL}/api", httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
|
||||
{
|
||||
var client = ConstructSecretsServiceClient();
|
||||
SecretModel secret = client.GetSecretAsync(false, true, secretID, null).Result;
|
||||
|
||||
// clear return variables
|
||||
secretDomain = "";
|
||||
secretUsername = "";
|
||||
secretPassword = "";
|
||||
privatekey = "";
|
||||
string privatekeypassphrase = "";
|
||||
|
||||
// parse data and extract what we need
|
||||
foreach (var item in secret.Items)
|
||||
{
|
||||
if (item.FieldName.ToLower().Equals("domain"))
|
||||
secretDomain = item.ItemValue;
|
||||
else if (item.FieldName.ToLower().Equals("username"))
|
||||
secretUsername = item.ItemValue;
|
||||
else if (item.FieldName.ToLower().Equals("password"))
|
||||
secretPassword = item.ItemValue;
|
||||
else if (item.FieldName.ToLower().Equals("private key"))
|
||||
{
|
||||
client.ReadResponseNoJSONConvert = true;
|
||||
privatekey = client.GetFieldAsync(false, false, secretID, "private-key").Result;
|
||||
client.ReadResponseNoJSONConvert = false;
|
||||
}
|
||||
else if (item.FieldName.ToLower().Equals("private key passphrase"))
|
||||
privatekeypassphrase = item.ItemValue;
|
||||
}
|
||||
|
||||
// need to decode the private key?
|
||||
if (!string.IsNullOrEmpty(privatekeypassphrase))
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
|
||||
privatekey = key;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// conversion to putty format necessary?
|
||||
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
|
||||
{
|
||||
try
|
||||
{
|
||||
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
|
||||
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region PUTTY KEY HANDLING
|
||||
// decode rsa private key with encryption password
|
||||
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
|
||||
{
|
||||
TextReader textReader = new StringReader(encryptedPrivateKey);
|
||||
PemReader pemReader = new(textReader, new PasswordFinder(password));
|
||||
|
||||
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
|
||||
|
||||
TextWriter textWriter = new StringWriter();
|
||||
var pemWriter = new PemWriter(textWriter);
|
||||
pemWriter.WriteObject(keyPair.Private);
|
||||
pemWriter.Writer.Flush();
|
||||
|
||||
return ""+textWriter.ToString();
|
||||
}
|
||||
private class PasswordFinder(string password) : IPasswordFinder
|
||||
{
|
||||
private string password = password;
|
||||
|
||||
public char[] GetPassword()
|
||||
{
|
||||
return password.ToCharArray();
|
||||
}
|
||||
}
|
||||
|
||||
// read private key pem string to rsacryptoserviceprovider
|
||||
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
|
||||
{
|
||||
PemReader pr = new(new StringReader(pem));
|
||||
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
|
||||
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
|
||||
RSACryptoServiceProvider rsa = new();
|
||||
rsa.ImportParameters(rsaParams);
|
||||
return rsa;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region TOKEN
|
||||
private static string GetToken()
|
||||
{
|
||||
// if there is no token, fetch a fresh one
|
||||
if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
|
||||
{
|
||||
return GetTokenFresh();
|
||||
}
|
||||
// if there is a token, check if it is valid
|
||||
if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
|
||||
{
|
||||
return SSConnectionData.ssTokenBearer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// try using refresh token
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
|
||||
TokenResponse token = new();
|
||||
try
|
||||
{
|
||||
token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
|
||||
var tokenResult = token.Access_token;
|
||||
|
||||
SSConnectionData.ssTokenBearer = tokenResult;
|
||||
SSConnectionData.ssTokenRefresh = token.Refresh_token;
|
||||
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
|
||||
return tokenResult;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// refresh token failed. clean memory and start fresh
|
||||
SSConnectionData.ssTokenBearer = "";
|
||||
SSConnectionData.ssTokenRefresh = "";
|
||||
SSConnectionData.ssTokenExpiresOn = DateTime.Now;
|
||||
// if OTP is required we need to ask user for a new OTP
|
||||
if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
|
||||
{
|
||||
SSConnectionData.initdone = false;
|
||||
// the call below executes a connection test, which fetches a valid token
|
||||
SSConnectionData.Init();
|
||||
// we now have a fresh token in memory. return it to caller
|
||||
return SSConnectionData.ssTokenBearer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no user interaction required. get a fresh token and return it to caller
|
||||
return GetTokenFresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static string GetTokenFresh()
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
// Authenticate:
|
||||
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
|
||||
// call below will throw an exception if the creds are invalid
|
||||
var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
|
||||
// here we can be sure the creds are ok - return success state
|
||||
var tokenResult = token.Access_token;
|
||||
|
||||
SSConnectionData.ssTokenBearer = tokenResult;
|
||||
SSConnectionData.ssTokenRefresh = token.Refresh_token;
|
||||
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
|
||||
return tokenResult;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
// input must be the secret id to fetch
|
||||
public static void FetchSecretFromServer(string input, out string username, out string password, out string domain, out string privatekey)
|
||||
{
|
||||
// get secret id
|
||||
int secretID = Int32.Parse(input);
|
||||
|
||||
// init connection credentials, display popup if necessary
|
||||
SSConnectionData.Init();
|
||||
|
||||
// get the secret
|
||||
FetchSecret(secretID, out username, out password, out domain, out privatekey);
|
||||
}
|
||||
}
|
||||
128358
ExternalConnectors/DSS/SecretServerRestClient.cs
Normal file
33
ExternalConnectors/ExternalConnectors.csproj
Normal file
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<Platforms>x64;arm64</Platforms>
|
||||
<Configurations>Debug;Release;Debug Portable;Release Self-Contained;Deploy to github</Configurations>
|
||||
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.Core" />
|
||||
<PackageReference Include="AWSSDK.EC2" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="VaultSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="AWS\AWSConnectionForm.cs" />
|
||||
<Compile Update="CPS\CPSConnectionForm.cs" />
|
||||
<Compile Update="DSS\SSConnectionForm.cs" />
|
||||
<Compile Update="VO\VaultOpenbaoConnectionForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
131
ExternalConnectors/OP/OnePasswordCli.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
|
||||
namespace ExternalConnectors.OP;
|
||||
|
||||
public class OnePasswordCliException(string message, string arguments) : Exception(message)
|
||||
{
|
||||
public string Arguments { get; set; } = arguments;
|
||||
}
|
||||
|
||||
public class OnePasswordCli
|
||||
{
|
||||
private const string OnePasswordCliExecutable = "op.exe";
|
||||
|
||||
// Username / password purpose metadata is used on Login category item fields
|
||||
private const string UserNamePurpose = "USERNAME";
|
||||
private const string PasswordPurpose = "PASSWORD";
|
||||
|
||||
// Server category items (and perhaps others) do have a built-in username/password field but don't have the `purpose` set
|
||||
// and because it's a built-in field this can't be set afterwards.
|
||||
// We use the label for as fallback because that can be user-modified to fit this convention in all cases.
|
||||
private const string UserNameLabel = "username";
|
||||
private const string PasswordLabel = "password";
|
||||
|
||||
|
||||
private const string StringType = "STRING";
|
||||
private const string SshKeyType = "SSHKEY";
|
||||
private const string DomainLabel = "domain";
|
||||
|
||||
private record VaultUrl(string Label, string Href);
|
||||
|
||||
private record VaultField(string Id, string Label, string Type, string Purpose, string Value);
|
||||
|
||||
private record VaultItem(VaultUrl[]? Urls, VaultField[]? Fields);
|
||||
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static void ReadPassword(string input, out string username, out string password, out string domain, out string privateKey)
|
||||
{
|
||||
var inputUrl = new Uri(input);
|
||||
var vault = WebUtility.UrlDecode(inputUrl.Host);
|
||||
var queryParams = HttpUtility.ParseQueryString(inputUrl.Query);
|
||||
var account = queryParams["account"];
|
||||
var item = WebUtility.UrlDecode(inputUrl.AbsolutePath.TrimStart('/'));
|
||||
ItemGet(item, vault, account, out username, out password, out domain, out privateKey);
|
||||
}
|
||||
|
||||
private static void ItemGet(string item, string? vault, string? account, out string username, out string password, out string domain, out string privateKey)
|
||||
{
|
||||
var args = new List<string> { "item", "get", item };
|
||||
|
||||
if (!string.IsNullOrEmpty(account))
|
||||
{
|
||||
args.Add("--account");
|
||||
args.Add(account);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vault))
|
||||
{
|
||||
args.Add("--vault");
|
||||
args.Add(vault);
|
||||
}
|
||||
|
||||
args.Add("--format");
|
||||
args.Add("json");
|
||||
|
||||
string commandLine = OnePasswordCliExecutable + " " + string.Join(' ', args);
|
||||
|
||||
var exitCode = RunCommand(OnePasswordCliExecutable, args, out var output, out var error);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
username = string.Empty;
|
||||
password = string.Empty;
|
||||
privateKey = string.Empty;
|
||||
domain = string.Empty;
|
||||
throw new OnePasswordCliException($"Error running op item get: {error}",
|
||||
commandLine);
|
||||
}
|
||||
|
||||
var items = JsonSerializer.Deserialize<VaultItem>(output, JsonSerializerOptions) ??
|
||||
throw new OnePasswordCliException("1Password returned null",
|
||||
commandLine);
|
||||
username = FindField(items, UserNamePurpose, UserNameLabel);
|
||||
password = FindField(items, PasswordPurpose, PasswordLabel);
|
||||
privateKey = items.Fields?.FirstOrDefault(x => x.Type == SshKeyType)?.Value ?? string.Empty;
|
||||
domain = items.Fields?.FirstOrDefault(x => x.Type == StringType && x.Label == DomainLabel)?.Value ?? string.Empty;
|
||||
if(string.IsNullOrEmpty(password) && string.IsNullOrEmpty(privateKey))
|
||||
{
|
||||
throw new OnePasswordCliException("No secret found in 1Password. At least fields with labels username/password or a SshKey are expected.", commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindField(VaultItem items, string purpose, string fallbackLabel)
|
||||
{
|
||||
return items.Fields?.FirstOrDefault(x => x.Purpose == purpose)?.Value ??
|
||||
items.Fields?.FirstOrDefault(x => x.Type == StringType && string.Equals(x.Id, fallbackLabel, StringComparison.InvariantCultureIgnoreCase))?.Value ??
|
||||
items.Fields?.FirstOrDefault(x => x.Type == StringType && string.Equals(x.Label, fallbackLabel, StringComparison.InvariantCultureIgnoreCase))?.Value ??
|
||||
string.Empty;
|
||||
}
|
||||
|
||||
private static int RunCommand(string command, IReadOnlyCollection<string> arguments, out string output,
|
||||
out string error)
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = command,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
foreach (var argument in arguments)
|
||||
{
|
||||
processStartInfo.ArgumentList.Add(argument);
|
||||
}
|
||||
|
||||
using var process = new Process();
|
||||
process.StartInfo = processStartInfo;
|
||||
process.Start();
|
||||
output = process.StandardOutput.ReadToEnd();
|
||||
error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
111
ExternalConnectors/PuttyKeyFileGenerator.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ExternalConnectors;
|
||||
|
||||
public class PuttyKeyFileGenerator
|
||||
{
|
||||
private const int prefixSize = 4;
|
||||
private const int paddedPrefixSize = prefixSize + 1;
|
||||
private const int lineLength = 64;
|
||||
private const string keyType = "ssh-rsa";
|
||||
private const string encryptionType = "none";
|
||||
|
||||
public static string ToPuttyPrivateKey(RSACryptoServiceProvider cryptoServiceProvider, string Comment = "imported-openssh-key")
|
||||
{
|
||||
var publicParameters = cryptoServiceProvider.ExportParameters(false);
|
||||
byte[] publicBuffer = new byte[3 + keyType.Length + GetPrefixSize(publicParameters.Exponent) + publicParameters.Exponent!.Length + GetPrefixSize(publicParameters.Modulus) + publicParameters.Modulus!.Length + 1];
|
||||
|
||||
using (var bw = new BinaryWriter(new MemoryStream(publicBuffer)))
|
||||
{
|
||||
bw.Write(new byte[] { 0x00, 0x00, 0x00 });
|
||||
bw.Write(Encoding.ASCII.GetBytes(keyType));
|
||||
PutPrefixed(bw, publicParameters.Exponent, CheckIsNeddPadding(publicParameters.Exponent));
|
||||
PutPrefixed(bw, publicParameters.Modulus, CheckIsNeddPadding(publicParameters.Modulus));
|
||||
}
|
||||
var publicBlob = System.Convert.ToBase64String(publicBuffer);
|
||||
|
||||
var privateParameters = cryptoServiceProvider.ExportParameters(true);
|
||||
|
||||
byte[] privateBuffer = new byte[paddedPrefixSize + privateParameters.D!.Length + paddedPrefixSize + privateParameters.P!.Length + paddedPrefixSize + privateParameters.Q!.Length + paddedPrefixSize + privateParameters.InverseQ!.Length];
|
||||
|
||||
using (var bw = new BinaryWriter(new MemoryStream(privateBuffer)))
|
||||
{
|
||||
PutPrefixed(bw, privateParameters.D, true);
|
||||
PutPrefixed(bw, privateParameters.P, true);
|
||||
PutPrefixed(bw, privateParameters.Q, true);
|
||||
PutPrefixed(bw, privateParameters.InverseQ, true);
|
||||
}
|
||||
var privateBlob = System.Convert.ToBase64String(privateBuffer);
|
||||
|
||||
HMACSHA1 hmacSha1 = new(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes("putty-private-key-file-mac-key")));
|
||||
byte[] bytesToHash = new byte[prefixSize + keyType.Length + prefixSize + encryptionType.Length + prefixSize + Comment.Length + prefixSize + publicBuffer.Length + prefixSize + privateBuffer.Length];
|
||||
|
||||
using (var bw = new BinaryWriter(new MemoryStream(bytesToHash)))
|
||||
{
|
||||
PutPrefixed(bw, Encoding.ASCII.GetBytes(keyType));
|
||||
PutPrefixed(bw, Encoding.ASCII.GetBytes(encryptionType));
|
||||
PutPrefixed(bw, Encoding.ASCII.GetBytes(Comment));
|
||||
PutPrefixed(bw, publicBuffer);
|
||||
PutPrefixed(bw, privateBuffer);
|
||||
}
|
||||
|
||||
var hash = string.Join("", hmacSha1.ComputeHash(bytesToHash).Select(x => $"{x:x2}"));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("PuTTY-User-Key-File-2: " + keyType);
|
||||
sb.AppendLine("Encryption: " + encryptionType);
|
||||
sb.AppendLine("Comment: " + Comment);
|
||||
|
||||
var publicLines = SpliceText(publicBlob, lineLength);
|
||||
sb.AppendLine("Public-Lines: " + publicLines.Length);
|
||||
foreach (var line in publicLines)
|
||||
{
|
||||
sb.AppendLine(line);
|
||||
}
|
||||
|
||||
var privateLines = SpliceText(privateBlob, lineLength);
|
||||
sb.AppendLine("Private-Lines: " + privateLines.Length);
|
||||
foreach (var line in privateLines)
|
||||
{
|
||||
sb.AppendLine(line);
|
||||
}
|
||||
|
||||
sb.AppendLine("Private-MAC: " + hash);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void PutPrefixed(BinaryWriter bw, byte[] bytes, bool addLeadingNull = false)
|
||||
{
|
||||
bw.Write(BitConverter.GetBytes(bytes.Length + (addLeadingNull ? 1 : 0)).Reverse().ToArray());
|
||||
if (addLeadingNull)
|
||||
bw.Write(new byte[] { 0x00 });
|
||||
bw.Write(bytes);
|
||||
}
|
||||
|
||||
private static string[] SpliceText(string text, int lineLength)
|
||||
{
|
||||
return Regex.Matches(text, ".{1," + lineLength + "}").Cast<Match>().Select(m => m.Value).ToArray();
|
||||
}
|
||||
|
||||
private static int GetPrefixSize(byte[]? bytes)
|
||||
{
|
||||
if (bytes is null)
|
||||
return 0;
|
||||
|
||||
return CheckIsNeddPadding(bytes) ? paddedPrefixSize : prefixSize;
|
||||
}
|
||||
|
||||
private static bool CheckIsNeddPadding(byte[] bytes)
|
||||
{
|
||||
if (bytes is null || bytes.Length == 0)
|
||||
return false;
|
||||
|
||||
// 128 == 10000000
|
||||
// This means that the number of bits can be divided by 8.
|
||||
// According to the algorithm in putty, you need to add a padding.
|
||||
return bytes[0] >= 128;
|
||||
}
|
||||
}
|
||||
105
ExternalConnectors/VO/VaultOpenbao.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Microsoft.Win32;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using VaultSharp;
|
||||
using VaultSharp.V1.AuthMethods;
|
||||
using VaultSharp.V1.AuthMethods.Token;
|
||||
|
||||
namespace ExternalConnectors.VO {
|
||||
public class VaultOpenbaoException(string message, string? arguments = null) : Exception(message) {
|
||||
public string Arguments { get; set; } = arguments ?? string.Empty;
|
||||
}
|
||||
|
||||
public static class VaultOpenbao {
|
||||
private static readonly RegistryKey baseKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteVaultOpenbao");
|
||||
private static string token = "";
|
||||
private static VaultClient GetClient() {
|
||||
string url = (string)baseKey.GetValue("URL", "");
|
||||
using VaultOpenbaoConnectionForm voForm = new();
|
||||
voForm.tbUrl.Text = url;
|
||||
voForm.tbToken.Text = token;
|
||||
_ = voForm.ShowDialog();
|
||||
if (voForm.DialogResult != DialogResult.OK)
|
||||
throw new VaultOpenbaoException($"No credential provided");
|
||||
url = voForm.tbUrl.Text;
|
||||
if (!string.IsNullOrEmpty(voForm.tbToken.Text)) // override token if provided
|
||||
token = voForm.tbToken.Text;
|
||||
IAuthMethodInfo authMethod = new TokenAuthMethodInfo(token);
|
||||
var vaultClientSettings = new VaultClientSettings(url, authMethod);
|
||||
VaultClient client = new(vaultClientSettings);
|
||||
var sysInfo = client.V1.System.GetInitStatusAsync().Result;
|
||||
if (!sysInfo) {
|
||||
MessageBox.Show("Test connection failed", "Vault Openbao", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
throw new VaultOpenbaoException("Url not working");
|
||||
}
|
||||
baseKey.SetValue("URL", url);
|
||||
return client;
|
||||
}
|
||||
private static void TestMountType(VaultClient vaultClient, string mount, int VaultOpenbaoSecretEngine) {
|
||||
switch (vaultClient.V1.System.GetSecretBackendAsync(mount).Result.Data.Type.Type) {
|
||||
case "kv" when VaultOpenbaoSecretEngine != 0:
|
||||
throw new VaultOpenbaoException($"Backend of type kv does not match expected type {VaultOpenbaoSecretEngine}");
|
||||
case "ldap" when VaultOpenbaoSecretEngine != 1 && VaultOpenbaoSecretEngine != 2:
|
||||
throw new VaultOpenbaoException($"Backend of type ldap does not match expected type {VaultOpenbaoSecretEngine}");
|
||||
case "ssh" when VaultOpenbaoSecretEngine != 3:
|
||||
throw new VaultOpenbaoException($"Backend of type ssh does not match expected type {VaultOpenbaoSecretEngine}");
|
||||
}
|
||||
}
|
||||
public static void ReadOtpSSH(string mount, string role, string? username, string address, out string password) {
|
||||
VaultClient vaultClient = GetClient();
|
||||
TestMountType(vaultClient, mount, 3);
|
||||
if (!IPAddress.TryParse(address, out _)) {
|
||||
try {
|
||||
var addrs = Dns.GetHostAddressesAsync(address).Result;
|
||||
if (addrs == null || addrs.Length == 0) {
|
||||
throw new VaultOpenbaoException($"Could not resolve address '{address}'");
|
||||
}
|
||||
// Prefer IPv4, otherwise take first available
|
||||
var selected = addrs.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) ?? addrs[0];
|
||||
address = selected.ToString();
|
||||
} catch (Exception ex) {
|
||||
throw new VaultOpenbaoException($"Failed to resolve address '{address}'", ex.Message);
|
||||
}
|
||||
}
|
||||
var otp = vaultClient.V1.Secrets.SSH.GetCredentialsAsync(role, address, username, mount).Result;
|
||||
password = otp.Data.Key;
|
||||
|
||||
}
|
||||
public static void ReadPasswordSSH(int secretEngine, string mount, string role, string username, out string password) {
|
||||
VaultClient vaultClient = GetClient();
|
||||
TestMountType(vaultClient, mount, secretEngine);
|
||||
switch (secretEngine) {
|
||||
case 0:
|
||||
var kv = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(role, mountPoint: mount).Result;
|
||||
password = kv.Data.Data[username].ToString() ?? string.Empty;
|
||||
return;
|
||||
default:
|
||||
throw new VaultOpenbaoException($"Backend of type {secretEngine} is not supported");
|
||||
}
|
||||
}
|
||||
public static void ReadPasswordRDP(int secretEngine, string mount, string role, ref string username, out string password) {
|
||||
VaultClient vaultClient = GetClient();
|
||||
TestMountType(vaultClient, mount, secretEngine);
|
||||
switch (secretEngine) {
|
||||
case 0:
|
||||
var kv = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(role, mountPoint: mount).Result;
|
||||
password = kv.Data.Data[username].ToString() ?? string.Empty;
|
||||
return;
|
||||
case 1:
|
||||
var ldapd = vaultClient.V1.Secrets.OpenLDAP.GetDynamicCredentialsAsync(role, mount).Result;
|
||||
username = ldapd.Data.Username;
|
||||
password = ldapd.Data.Password;
|
||||
return;
|
||||
case 2:
|
||||
var ldaps = vaultClient.V1.Secrets.OpenLDAP.GetStaticCredentialsAsync(role, mount).Result;
|
||||
username = ldaps.Data.Username;
|
||||
password = ldaps.Data.Password;
|
||||
return;
|
||||
default:
|
||||
throw new VaultOpenbaoException($"Backend of type {secretEngine} is not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
185
ExternalConnectors/VO/VaultOpenbaoConnectionForm.Designer.cs
generated
Normal file
@@ -0,0 +1,185 @@
|
||||
namespace ExternalConnectors.VO
|
||||
{
|
||||
partial class VaultOpenbaoConnectionForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
tbUrl = new TextBox();
|
||||
tbToken = new TextBox();
|
||||
btnOK = new Button();
|
||||
btnCancel = new Button();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
label1 = new Label();
|
||||
label2 = new Label();
|
||||
tableLayoutPanel2 = new TableLayoutPanel();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// tbUrl
|
||||
//
|
||||
tbUrl.Dock = DockStyle.Fill;
|
||||
tbUrl.Location = new Point(174, 5);
|
||||
tbUrl.Margin = new Padding(5);
|
||||
tbUrl.Name = "tbUrl";
|
||||
tbUrl.Size = new Size(559, 27);
|
||||
tbUrl.TabIndex = 0;
|
||||
//
|
||||
// tbToken
|
||||
//
|
||||
tbToken.Dock = DockStyle.Fill;
|
||||
tbToken.Location = new Point(174, 57);
|
||||
tbToken.Margin = new Padding(5);
|
||||
tbToken.Name = "tbToken";
|
||||
tbToken.Size = new Size(559, 27);
|
||||
tbToken.TabIndex = 2;
|
||||
tbToken.UseSystemPasswordChar = true;
|
||||
tbToken.Focus();
|
||||
//
|
||||
// btnOK
|
||||
//
|
||||
btnOK.Anchor = AnchorStyles.Right;
|
||||
btnOK.DialogResult = DialogResult.OK;
|
||||
btnOK.Location = new Point(250, 16);
|
||||
btnOK.Margin = new Padding(5);
|
||||
btnOK.Name = "btnOK";
|
||||
btnOK.Size = new Size(101, 35);
|
||||
btnOK.TabIndex = 10;
|
||||
btnOK.Text = "OK";
|
||||
btnOK.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
btnCancel.Anchor = AnchorStyles.Left;
|
||||
btnCancel.DialogResult = DialogResult.Cancel;
|
||||
btnCancel.Location = new Point(387, 16);
|
||||
btnCancel.Margin = new Padding(5);
|
||||
btnCancel.Name = "btnCancel";
|
||||
btnCancel.Size = new Size(101, 35);
|
||||
btnCancel.TabIndex = 11;
|
||||
btnCancel.Text = "Cancel";
|
||||
btnCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
tableLayoutPanel1.ColumnCount = 2;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 22.92994F));
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 77.07006F));
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(tbUrl, 1, 0);
|
||||
tableLayoutPanel1.Controls.Add(tbToken, 1, 1);
|
||||
tableLayoutPanel1.Dock = DockStyle.Top;
|
||||
tableLayoutPanel1.Location = new Point(0, 0);
|
||||
tableLayoutPanel1.Margin = new Padding(5);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 3;
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
|
||||
tableLayoutPanel1.Size = new Size(738, 136);
|
||||
tableLayoutPanel1.TabIndex = 12;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.AutoSize = true;
|
||||
label1.Dock = DockStyle.Fill;
|
||||
label1.Location = new Point(5, 0);
|
||||
label1.Margin = new Padding(5, 0, 5, 0);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(159, 52);
|
||||
label1.TabIndex = 2;
|
||||
label1.Text = "Server URL";
|
||||
label1.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.AutoSize = true;
|
||||
label2.Dock = DockStyle.Fill;
|
||||
label2.Location = new Point(5, 52);
|
||||
label2.Margin = new Padding(5, 0, 5, 0);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new Size(159, 52);
|
||||
label2.TabIndex = 4;
|
||||
label2.Text = "Access Token";
|
||||
label2.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
tableLayoutPanel2.ColumnCount = 5;
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
|
||||
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
|
||||
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||
tableLayoutPanel2.Location = new Point(0, 149);
|
||||
tableLayoutPanel2.Margin = new Padding(5);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel2.Size = new Size(738, 67);
|
||||
tableLayoutPanel2.TabIndex = 13;
|
||||
//
|
||||
// VaultOpenbaoConnectionForm
|
||||
//
|
||||
AcceptButton = btnOK;
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(738, 216);
|
||||
Controls.Add(tableLayoutPanel2);
|
||||
Controls.Add(tableLayoutPanel1);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Margin = new Padding(5);
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "VaultOpenbaoConnectionForm";
|
||||
SizeGripStyle = SizeGripStyle.Hide;
|
||||
Text = "Vault/Openbao API Login Data";
|
||||
Activated += VaultOpenbaoConnectionForm_Activated;
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public System.Windows.Forms.TextBox tbUrl;
|
||||
public System.Windows.Forms.TextBox tbToken;
|
||||
private System.Windows.Forms.Button btnOK;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||
}
|
||||
}
|
||||
13
ExternalConnectors/VO/VaultOpenbaoConnectionForm.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace ExternalConnectors.VO
|
||||
{
|
||||
public partial class VaultOpenbaoConnectionForm : Form {
|
||||
public VaultOpenbaoConnectionForm() {
|
||||
InitializeComponent();
|
||||
|
||||
}
|
||||
|
||||
private void VaultOpenbaoConnectionForm_Activated(object sender, EventArgs e) {
|
||||
tbUrl.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -117,7 +117,4 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>48</value>
|
||||
</metadata>
|
||||
</root>
|
||||
158
IMPLEMENTATION_NOTES.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Connection Frame Color Feature - Implementation Summary
|
||||
|
||||
## Overview
|
||||
This implementation adds a "Connection Frame Color" feature to mRemoteNG that allows users to visually distinguish between different connection environments (e.g., production, test, development) by displaying a colored border around connection panels.
|
||||
|
||||
## Feature Request Context
|
||||
The feature was requested in issue to prevent accidental operations on production systems. The user cited DBeaver database management tool as an example, which uses a red frame to indicate production database connections.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Data Model (Connection/ConnectionFrameColor.cs)
|
||||
Created an enum with the following values:
|
||||
- **None** (default): No colored border
|
||||
- **Red**: Intended for production environments
|
||||
- **Yellow**: Intended for staging/UAT environments
|
||||
- **Green**: Intended for test environments
|
||||
- **Blue**: Intended for development environments
|
||||
- **Purple**: Intended for custom/other environments
|
||||
|
||||
### 2. Property Addition (Connection/AbstractConnectionRecord.cs)
|
||||
- Added `ConnectionFrameColor` property to the base connection record class
|
||||
- Property is categorized under "Display" section in PropertyGrid
|
||||
- Uses EnumTypeConverter for proper display in PropertyGrid
|
||||
- Includes localized descriptions
|
||||
|
||||
### 3. Inheritance Support (Connection/ConnectionInfoInheritance.cs)
|
||||
- Added `ConnectionFrameColor` inheritance property
|
||||
- Allows folders to set a frame color that child connections can inherit
|
||||
- Follows the same pattern as other inheritable properties
|
||||
|
||||
### 4. Serialization
|
||||
|
||||
#### XML Serialization (Config/Serializers/ConnectionSerializers/Xml/)
|
||||
- **XmlConnectionNodeSerializer28.cs**: Serializes ConnectionFrameColor as an XML attribute
|
||||
- **XmlConnectionsDeserializer.cs**: Deserializes ConnectionFrameColor from XML
|
||||
- Includes inheritance attribute handling (InheritConnectionFrameColor)
|
||||
- Backward compatible: old files without this attribute will default to None
|
||||
|
||||
#### CSV Serialization (Config/Serializers/ConnectionSerializers/Csv/)
|
||||
- **CsvConnectionsSerializerMremotengFormat.cs**: Added ConnectionFrameColor to CSV export
|
||||
- Includes both value and inheritance columns
|
||||
- Maintains CSV column order consistency
|
||||
|
||||
### 5. Visual Rendering (Connection/InterfaceControl.cs)
|
||||
- Added custom Paint event handler to InterfaceControl
|
||||
- Draws a 4-pixel colored border around the connection panel when ConnectionFrameColor is set
|
||||
- Uses specific colors:
|
||||
- Red: RGB(220, 53, 69) - Bootstrap danger red
|
||||
- Yellow: RGB(255, 193, 7) - Warning yellow
|
||||
- Green: RGB(40, 167, 69) - Success green
|
||||
- Blue: RGB(0, 123, 255) - Primary blue
|
||||
- Purple: RGB(111, 66, 193) - Purple
|
||||
- Border is drawn inside the control bounds to avoid clipping
|
||||
|
||||
### 6. Localization (Language/Language.resx)
|
||||
Added language resources for:
|
||||
- ConnectionFrameColor: "Connection Frame Color"
|
||||
- PropertyDescriptionConnectionFrameColor: Description shown in PropertyGrid
|
||||
- FrameColorNone: "None"
|
||||
- FrameColorRed: "Red (Production)"
|
||||
- FrameColorYellow: "Yellow (Staging/UAT)"
|
||||
- FrameColorGreen: "Green (Test)"
|
||||
- FrameColorBlue: "Blue (Development)"
|
||||
- FrameColorPurple: "Purple (Custom)"
|
||||
|
||||
### 7. Documentation (mRemoteNGDocumentation/howtos/connection_frame_color.rst)
|
||||
Created comprehensive documentation including:
|
||||
- Overview and purpose
|
||||
- Step-by-step usage instructions
|
||||
- Visual examples
|
||||
- Inheritance explanation
|
||||
- Best practices for environment organization
|
||||
- Troubleshooting guide
|
||||
|
||||
## Technical Design Decisions
|
||||
|
||||
### Why 4-pixel border?
|
||||
- Wide enough to be immediately noticeable
|
||||
- Not so wide as to obscure content
|
||||
- Consistent with modern UI design patterns
|
||||
|
||||
### Why these specific colors?
|
||||
- Colors chosen based on common conventions:
|
||||
- Red = danger/production (universal warning color)
|
||||
- Yellow = caution/staging (standard warning color)
|
||||
- Green = safe/test (universal "go" color)
|
||||
- Blue = development (calm, neutral)
|
||||
- Purple = custom (distinct but not alarming)
|
||||
- Colors use accessible, high-contrast RGB values
|
||||
|
||||
### Why enum instead of custom color picker?
|
||||
- Simpler UI (dropdown vs color picker)
|
||||
- Ensures consistency across team/organization
|
||||
- Prevents confusion from too many color choices
|
||||
- Follows principle of "convention over configuration"
|
||||
- Can be extended in future if needed
|
||||
|
||||
### Why inherit from Panel?
|
||||
- InterfaceControl is already a Panel (see InterfaceControl.Designer.cs)
|
||||
- Panel has built-in Paint event support
|
||||
- No need for additional controls or complexity
|
||||
|
||||
## Backward Compatibility
|
||||
- Old connection files (without ConnectionFrameColor attribute) automatically default to None
|
||||
- No migration needed
|
||||
- Feature is completely opt-in
|
||||
- Does not affect existing functionality
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
When testing this feature, verify:
|
||||
|
||||
1. **Property Display**: ConnectionFrameColor appears in PropertyGrid under Display section
|
||||
2. **Enum Values**: All color options appear in dropdown
|
||||
3. **Visual Rendering**: Border appears when color is selected and connection is active
|
||||
4. **Inheritance**: Setting on folder and enabling inheritance on child works correctly
|
||||
5. **Serialization**:
|
||||
- Save connection with frame color set
|
||||
- Close and reopen file
|
||||
- Verify color is preserved
|
||||
6. **CSV Export**: ConnectionFrameColor appears in exported CSV
|
||||
7. **Backward Compatibility**: Open old connection files without errors
|
||||
|
||||
## Future Enhancements (Out of Scope)
|
||||
|
||||
Potential future improvements:
|
||||
- Custom color picker support
|
||||
- Border width customization
|
||||
- Border style options (solid, dashed, etc.)
|
||||
- Tab header color indicator in addition to panel border
|
||||
- Global warning when connecting to production (confirmation dialog)
|
||||
- Audit logging for production connections
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. mRemoteNG/Connection/ConnectionFrameColor.cs (NEW)
|
||||
2. mRemoteNG/Connection/AbstractConnectionRecord.cs
|
||||
3. mRemoteNG/Connection/ConnectionInfo.cs
|
||||
4. mRemoteNG/Connection/ConnectionInfoInheritance.cs
|
||||
5. mRemoteNG/Connection/InterfaceControl.cs
|
||||
6. mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs
|
||||
7. mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
|
||||
8. mRemoteNG/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
|
||||
9. mRemoteNG/Language/Language.resx
|
||||
10. mRemoteNGDocumentation/howtos/connection_frame_color.rst (NEW)
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [x] Property follows existing naming conventions
|
||||
- [x] Enum values are localized
|
||||
- [x] Inheritance support implemented
|
||||
- [x] XML serialization/deserialization working
|
||||
- [x] CSV serialization updated
|
||||
- [x] Visual rendering implemented
|
||||
- [x] Documentation created
|
||||
- [x] Backward compatibility maintained
|
||||
- [x] No breaking changes
|
||||
- [x] Code follows existing patterns in codebase
|
||||
63
Jenkinsfile
vendored
@@ -1,63 +0,0 @@
|
||||
#!groovy
|
||||
node('windows') {
|
||||
def jobDir = pwd()
|
||||
def solutionFilePath = "\"${jobDir}\\mRemoteV1.sln\""
|
||||
def msBuild = "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\MSBuild\\15.0\\Bin\\msbuild.exe"
|
||||
def nunitConsolePath = "${jobDir}\\packages\\NUnit.ConsoleRunner.3.7.0\\tools\\nunit3-console.exe"
|
||||
def openCoverPath = "${jobDir}\\packages\\OpenCover.4.6.519\\tools\\OpenCover.Console.exe"
|
||||
def reportGeneratorPath = "${jobDir}\\packages\\ReportGenerator.3.0.2\\tools\\ReportGenerator.exe"
|
||||
def testResultFilePrefix = "TestResult"
|
||||
def testResultFileNormal = "${testResultFilePrefix}_UnitTests_normal.xml"
|
||||
def testResultFilePortable = "${testResultFilePrefix}_UnitTests_portable.xml"
|
||||
def testResultFileAcceptance = "${testResultFilePrefix}_AcceptanceTests.xml"
|
||||
def coverageReport = "code_coverage_report.xml"
|
||||
def codeCoverageHtml = "CodeCoverageReport.html"
|
||||
|
||||
stage ('Checkout Branch') {
|
||||
checkout scm
|
||||
bat "del /Q \"${jobDir}\\${testResultFilePrefix}*.xml\""
|
||||
}
|
||||
|
||||
stage ('Restore NuGet Packages') {
|
||||
def nugetPath = "C:\\nuget.exe"
|
||||
bat "${nugetPath} restore ${solutionFilePath}"
|
||||
}
|
||||
|
||||
stage ('Build mRemoteNG (Normal)') {
|
||||
bat "\"${msBuild}\" /nologo /p:Platform=x86 \"${jobDir}\\mRemoteV1.sln\""
|
||||
}
|
||||
|
||||
stage ('Build mRemoteNG (Portable)') {
|
||||
bat "\"${msBuild}\" /nologo /p:Configuration=\"Debug Portable\";Platform=x86 \"${jobDir}\\mRemoteV1.sln\""
|
||||
}
|
||||
|
||||
stage ('Run Unit Tests (Normal, w/coverage)') {
|
||||
try {
|
||||
bat "\"${openCoverPath}\" -register:user -target:\"${nunitConsolePath}\" -targetargs:\"\"${jobDir}\\mRemoteNGTests\\bin\\debug\\mRemoteNGTests.dll\" --result=${testResultFileNormal} --x86\" -output:\"${coverageReport}\""
|
||||
}
|
||||
catch (ex) {
|
||||
nunit testResultsPattern: "${testResultFilePrefix}*.xml"
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Run Unit Tests (Portable)') {
|
||||
try {
|
||||
bat "\"${nunitConsolePath}\" \"${jobDir}\\mRemoteNGTests\\bin\\debug portable\\mRemoteNGTests.dll\" --result=${testResultFilePortable} --x86"
|
||||
}
|
||||
catch (ex) {
|
||||
nunit testResultsPattern: "${testResultFilePrefix}*.xml"
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Run Acceptance Tests') {
|
||||
try {
|
||||
bat "\"${nunitConsolePath}\" \"${jobDir}\\mRemoteNG.Specs\\bin\\debug\\mRemoteNG.Specs.dll\" --result=${testResultFileAcceptance} --x86"
|
||||
}
|
||||
catch (ex) {
|
||||
nunit testResultsPattern: "${testResultFilePrefix}*.xml"
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
node('windows') {
|
||||
def jobDir = pwd()
|
||||
def solutionFilePath = "\"${jobDir}\\mRemoteV1.sln\""
|
||||
def msBuild = "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\MSBuild\\15.0\\Bin\\msbuild.exe"
|
||||
def nunitConsolePath = "${jobDir}\\packages\\NUnit.ConsoleRunner.3.10.0\\tools\\nunit3-console.exe"
|
||||
def openCoverPath = "${jobDir}\\packages\\OpenCover.4.6.519\\tools\\OpenCover.Console.exe"
|
||||
def testResultFilePrefix = "TestResult"
|
||||
def testResultFileNormal = "${testResultFilePrefix}_UnitTests_normal.xml"
|
||||
def testResultFilePortable = "${testResultFilePrefix}_UnitTests_portable.xml"
|
||||
def coverageReport = "code_coverage_report.xml"
|
||||
|
||||
|
||||
stage ('Clean output dir') {
|
||||
bat script: "rmdir /S /Q \"${jobDir}\\Release\" 2>nul", returnStatus: true
|
||||
}
|
||||
|
||||
stage ('Checkout Branch') {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: '*/${TargetBranch}']],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [],
|
||||
submoduleCfg: [],
|
||||
userRemoteConfigs: [[
|
||||
credentialsId: '9c3fbff4-5b90-402f-a298-00e607fcec87',
|
||||
url: 'https://github.com/mRemoteNG/mRemoteNG.git'
|
||||
]]
|
||||
])
|
||||
}
|
||||
|
||||
stage ('Restore NuGet Packages') {
|
||||
def nugetPath = "C:\\nuget.exe"
|
||||
bat "${nugetPath} restore ${solutionFilePath}"
|
||||
}
|
||||
|
||||
withCredentials([file(credentialsId: '9b674d57-6792-48e3-984a-4d1bab2abb64', variable: 'CODE_SIGNING_CERT')]) {
|
||||
withCredentials([usernamePassword(credentialsId: '05b7449b-05c0-490f-8661-236242526e62', passwordVariable: 'MRNG_CERT_PASSWORD', usernameVariable: 'NO_USERNAME')]) {
|
||||
stage ('Build mRemoteNG (Normal - MSI)') {
|
||||
bat "\"${msBuild}\" /nologo /t:Clean,Build /p:Configuration=\"Release Installer\" /p:Platform=x86 /p:CertPath=\"${env.CODE_SIGNING_CERT}\" /p:CertPassword=${env.MRNG_CERT_PASSWORD} \"${jobDir}\\mRemoteV1.sln\""
|
||||
archiveArtifacts artifacts: "Release\\*.msi", caseSensitive: false, onlyIfSuccessful: true, fingerprint: true
|
||||
}
|
||||
|
||||
stage ('Build mRemoteNG (Portable)') {
|
||||
bat "\"${msBuild}\" /nologo /t:Clean,Build /p:Configuration=\"Release Portable\" /p:Platform=x86 /p:CertPath=\"${env.CODE_SIGNING_CERT}\" /p:CertPassword=${env.MRNG_CERT_PASSWORD} \"${jobDir}\\mRemoteV1.sln\""
|
||||
archiveArtifacts artifacts: "Release\\*.zip", caseSensitive: false, onlyIfSuccessful: true, fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Run Unit Tests (Normal - MSI)') {
|
||||
bat "\"${nunitConsolePath}\" \"${jobDir}\\mRemoteNGTests\\bin\\release\\mRemoteNGTests.dll\" --result=${testResultFileNormal} --x86"
|
||||
}
|
||||
|
||||
stage ('Run Unit Tests (Portable)') {
|
||||
bat "\"${nunitConsolePath}\" \"${jobDir}\\mRemoteNGTests\\bin\\release portable\\mRemoteNGTests.dll\" --result=${testResultFilePortable} --x86"
|
||||
}
|
||||
|
||||
stage ('Generate UpdateCheck Files') {
|
||||
bat "powershell -ExecutionPolicy Bypass -File \"${jobDir}\\Tools\\create_upg_chk_files.ps1\" -TagName \"${env.TagName}\" -UpdateChannel \"${env.UpdateChannel}\""
|
||||
archiveArtifacts artifacts: "Release\\*.txt", caseSensitive: false, onlyIfSuccessful: true
|
||||
}
|
||||
|
||||
stage ('Publish to GitHub') {
|
||||
withCredentials([string(credentialsId: '5443a369-dbe8-42d3-b4e8-04d0b4e9039a', variable: 'GH_AUTH_TOKEN')]) {
|
||||
def releaseFolder = "${jobDir}\\Release"
|
||||
// because batch files suck at handling newline characters, we have to convert to base64 in groovy and back to text in powershell
|
||||
def base64Description = env.ReleaseDescription.bytes.encodeBase64().toString()
|
||||
bat "powershell -ExecutionPolicy Bypass -File \"${jobDir}\\Tools\\publish_to_github.ps1\" -Owner \"mRemoteNG\" -Repository \"mRemoteNG\" -ReleaseTitle \"${env.ReleaseTitle}\" -TagName \"${env.TagName}\" -TargetCommitish \"${env.TargetBranch}\" -Description \"${base64Description}\" -IsDraft ${env.IsDraft} -IsPrerelease ${env.IsPreRelease} -ReleaseFolderPath \"${releaseFolder}\" -AuthToken \"${env.GH_AUTH_TOKEN}\" -DescriptionIsBase64Encoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
ObjectListView/AssemblyInfo.Version.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Reflection;
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("2.9.3.*")]
|
||||
[assembly: AssemblyFileVersion("2.9.3")]
|
||||
[assembly: AssemblyInformationalVersion("2.9.3")]
|
||||
[assembly: System.CLSCompliant(true)]
|
||||
520
ObjectListView/CellEditing/CellEditKeyEngine.cs
Normal file
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
* CellEditKeyEngine - A engine that allows the behaviour of arbitrary keys to be configured
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 3-March-2011 10:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* v2.8
|
||||
* 2014-05-30 JPP - When a row is disabled, skip over it when looking for another cell to edit
|
||||
* v2.5
|
||||
* 2012-04-14 JPP - Fixed bug where, on a OLV with only a single editable column, tabbing
|
||||
* to change rows would edit the cell above rather than the cell below
|
||||
* the cell being edited.
|
||||
* 2.5
|
||||
* 2011-03-03 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using BrightIdeasSoftware;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
/// <summary>
|
||||
/// Indicates the behavior of a key when a cell "on the edge" is being edited.
|
||||
/// and the normal behavior of that key would exceed the edge. For example,
|
||||
/// for a key that normally moves one column to the left, the "edge" would be
|
||||
/// the left most column, since the normal action of the key cannot be taken
|
||||
/// (since there are no more columns to the left).
|
||||
/// </summary>
|
||||
public enum CellEditAtEdgeBehaviour {
|
||||
/// <summary>
|
||||
/// The key press will be ignored
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will result in the cell editing wrapping to the
|
||||
/// cell on the opposite edge.
|
||||
/// </summary>
|
||||
Wrap,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will wrap, but the column will be changed to the
|
||||
/// appropiate adjacent column. This only makes sense for keys where
|
||||
/// the normal action is ChangeRow.
|
||||
/// </summary>
|
||||
ChangeColumn,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will wrap, but the row will be changed to the
|
||||
/// appropiate adjacent row. This only makes sense for keys where
|
||||
/// the normal action is ChangeColumn.
|
||||
/// </summary>
|
||||
ChangeRow,
|
||||
|
||||
/// <summary>
|
||||
/// The key will result in the current edit operation being ended.
|
||||
/// </summary>
|
||||
EndEdit
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the normal behaviour of a key when used during a cell edit
|
||||
/// operation.
|
||||
/// </summary>
|
||||
public enum CellEditCharacterBehaviour {
|
||||
/// <summary>
|
||||
/// The key press will be ignored
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will end the current edit and begin an edit
|
||||
/// operation on the next editable cell to the left.
|
||||
/// </summary>
|
||||
ChangeColumnLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will end the current edit and begin an edit
|
||||
/// operation on the next editable cell to the right.
|
||||
/// </summary>
|
||||
ChangeColumnRight,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will end the current edit and begin an edit
|
||||
/// operation on the row above.
|
||||
/// </summary>
|
||||
ChangeRowUp,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will end the current edit and begin an edit
|
||||
/// operation on the row below
|
||||
/// </summary>
|
||||
ChangeRowDown,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will cancel the current edit
|
||||
/// </summary>
|
||||
CancelEdit,
|
||||
|
||||
/// <summary>
|
||||
/// The key press will finish the current edit operation
|
||||
/// </summary>
|
||||
EndEdit,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb1,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb2,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb3,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb4,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb5,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb6,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb7,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb8,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb9,
|
||||
|
||||
/// <summary>
|
||||
/// Custom verb that can be used for specialized actions.
|
||||
/// </summary>
|
||||
CustomVerb10,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class handle key presses during a cell edit operation.
|
||||
/// </summary>
|
||||
public class CellEditKeyEngine {
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Sets the behaviour of a given key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="normalBehaviour"></param>
|
||||
/// <param name="atEdgeBehaviour"></param>
|
||||
public virtual void SetKeyBehaviour(Keys key, CellEditCharacterBehaviour normalBehaviour, CellEditAtEdgeBehaviour atEdgeBehaviour) {
|
||||
this.CellEditKeyMap[key] = normalBehaviour;
|
||||
this.CellEditKeyAtEdgeBehaviourMap[key] = atEdgeBehaviour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a key press
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="keyData"></param>
|
||||
/// <returns>True if the key was completely handled.</returns>
|
||||
public virtual bool HandleKey(ObjectListView olv, Keys keyData) {
|
||||
if (olv == null) throw new ArgumentNullException("olv");
|
||||
|
||||
CellEditCharacterBehaviour behaviour;
|
||||
if (!CellEditKeyMap.TryGetValue(keyData, out behaviour))
|
||||
return false;
|
||||
|
||||
this.ListView = olv;
|
||||
|
||||
switch (behaviour) {
|
||||
case CellEditCharacterBehaviour.Ignore:
|
||||
break;
|
||||
case CellEditCharacterBehaviour.CancelEdit:
|
||||
this.HandleCancelEdit();
|
||||
break;
|
||||
case CellEditCharacterBehaviour.EndEdit:
|
||||
this.HandleEndEdit();
|
||||
break;
|
||||
case CellEditCharacterBehaviour.ChangeColumnLeft:
|
||||
case CellEditCharacterBehaviour.ChangeColumnRight:
|
||||
this.HandleColumnChange(keyData, behaviour);
|
||||
break;
|
||||
case CellEditCharacterBehaviour.ChangeRowDown:
|
||||
case CellEditCharacterBehaviour.ChangeRowUp:
|
||||
this.HandleRowChange(keyData, behaviour);
|
||||
break;
|
||||
default:
|
||||
return this.HandleCustomVerb(keyData, behaviour);
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ObjectListView on which the current key is being handled.
|
||||
/// This cannot be null.
|
||||
/// </summary>
|
||||
protected ObjectListView ListView {
|
||||
get { return listView; }
|
||||
set { listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the row of the cell that is currently being edited
|
||||
/// </summary>
|
||||
protected OLVListItem ItemBeingEdited {
|
||||
get {
|
||||
return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? null : this.ListView.CellEditEventArgs.ListViewItem;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the column of the cell that is being edited
|
||||
/// </summary>
|
||||
protected int SubItemIndexBeingEdited {
|
||||
get {
|
||||
return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? -1 : this.ListView.CellEditEventArgs.SubItemIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map that remembers the normal behaviour of keys
|
||||
/// </summary>
|
||||
protected IDictionary<Keys, CellEditCharacterBehaviour> CellEditKeyMap {
|
||||
get {
|
||||
if (cellEditKeyMap == null)
|
||||
this.InitializeCellEditKeyMaps();
|
||||
return cellEditKeyMap;
|
||||
}
|
||||
set {
|
||||
cellEditKeyMap = value;
|
||||
}
|
||||
}
|
||||
private IDictionary<Keys, CellEditCharacterBehaviour> cellEditKeyMap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map that remembers the desired behaviour of keys
|
||||
/// on edge cases.
|
||||
/// </summary>
|
||||
protected IDictionary<Keys, CellEditAtEdgeBehaviour> CellEditKeyAtEdgeBehaviourMap {
|
||||
get {
|
||||
if (cellEditKeyAtEdgeBehaviourMap == null)
|
||||
this.InitializeCellEditKeyMaps();
|
||||
return cellEditKeyAtEdgeBehaviourMap;
|
||||
}
|
||||
set {
|
||||
cellEditKeyAtEdgeBehaviourMap = value;
|
||||
}
|
||||
}
|
||||
private IDictionary<Keys, CellEditAtEdgeBehaviour> cellEditKeyAtEdgeBehaviourMap;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Setup the default key mapping
|
||||
/// </summary>
|
||||
protected virtual void InitializeCellEditKeyMaps() {
|
||||
this.cellEditKeyMap = new Dictionary<Keys, CellEditCharacterBehaviour>();
|
||||
this.cellEditKeyMap[Keys.Escape] = CellEditCharacterBehaviour.CancelEdit;
|
||||
this.cellEditKeyMap[Keys.Return] = CellEditCharacterBehaviour.EndEdit;
|
||||
this.cellEditKeyMap[Keys.Enter] = CellEditCharacterBehaviour.EndEdit;
|
||||
this.cellEditKeyMap[Keys.Tab] = CellEditCharacterBehaviour.ChangeColumnRight;
|
||||
this.cellEditKeyMap[Keys.Tab | Keys.Shift] = CellEditCharacterBehaviour.ChangeColumnLeft;
|
||||
this.cellEditKeyMap[Keys.Left | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnLeft;
|
||||
this.cellEditKeyMap[Keys.Right | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnRight;
|
||||
this.cellEditKeyMap[Keys.Up | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowUp;
|
||||
this.cellEditKeyMap[Keys.Down | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowDown;
|
||||
|
||||
this.cellEditKeyAtEdgeBehaviourMap = new Dictionary<Keys, CellEditAtEdgeBehaviour>();
|
||||
this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab] = CellEditAtEdgeBehaviour.Wrap;
|
||||
this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab | Keys.Shift] = CellEditAtEdgeBehaviour.Wrap;
|
||||
this.cellEditKeyAtEdgeBehaviourMap[Keys.Left | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
|
||||
this.cellEditKeyAtEdgeBehaviourMap[Keys.Right | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
|
||||
this.cellEditKeyAtEdgeBehaviourMap[Keys.Up | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
|
||||
this.cellEditKeyAtEdgeBehaviourMap[Keys.Down | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Command handling
|
||||
|
||||
/// <summary>
|
||||
/// Handle the end edit command
|
||||
/// </summary>
|
||||
protected virtual void HandleEndEdit() {
|
||||
this.ListView.PossibleFinishCellEditing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the cancel edit command
|
||||
/// </summary>
|
||||
protected virtual void HandleCancelEdit() {
|
||||
this.ListView.CancelCellEdit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder that subclasses can override to handle any custom verbs
|
||||
/// </summary>
|
||||
/// <param name="keyData"></param>
|
||||
/// <param name="behaviour"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool HandleCustomVerb(Keys keyData, CellEditCharacterBehaviour behaviour) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a change row command
|
||||
/// </summary>
|
||||
/// <param name="keyData"></param>
|
||||
/// <param name="behaviour"></param>
|
||||
protected virtual void HandleRowChange(Keys keyData, CellEditCharacterBehaviour behaviour) {
|
||||
// If we couldn't finish editing the current cell, don't try to move it
|
||||
if (!this.ListView.PossibleFinishCellEditing())
|
||||
return;
|
||||
|
||||
OLVListItem olvi = this.ItemBeingEdited;
|
||||
int subItemIndex = this.SubItemIndexBeingEdited;
|
||||
bool isGoingUp = behaviour == CellEditCharacterBehaviour.ChangeRowUp;
|
||||
|
||||
// Try to find a row above (or below) the currently edited cell
|
||||
// If we find one, start editing it and we're done.
|
||||
OLVListItem adjacentOlvi = this.GetAdjacentItemOrNull(olvi, isGoingUp);
|
||||
if (adjacentOlvi != null) {
|
||||
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// There is no adjacent row in the direction we want, so we must be on an edge.
|
||||
CellEditAtEdgeBehaviour atEdgeBehaviour;
|
||||
if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
|
||||
atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
|
||||
switch (atEdgeBehaviour) {
|
||||
case CellEditAtEdgeBehaviour.Ignore:
|
||||
break;
|
||||
case CellEditAtEdgeBehaviour.EndEdit:
|
||||
this.ListView.PossibleFinishCellEditing();
|
||||
break;
|
||||
case CellEditAtEdgeBehaviour.Wrap:
|
||||
adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
|
||||
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
|
||||
break;
|
||||
case CellEditAtEdgeBehaviour.ChangeColumn:
|
||||
// Figure out the next editable column
|
||||
List<OLVColumn> editableColumnsInDisplayOrder = this.EditableColumnsInDisplayOrder;
|
||||
int displayIndex = Math.Max(0, editableColumnsInDisplayOrder.IndexOf(this.ListView.GetColumn(subItemIndex)));
|
||||
if (isGoingUp)
|
||||
displayIndex = (editableColumnsInDisplayOrder.Count + displayIndex - 1) % editableColumnsInDisplayOrder.Count;
|
||||
else
|
||||
displayIndex = (displayIndex + 1) % editableColumnsInDisplayOrder.Count;
|
||||
subItemIndex = editableColumnsInDisplayOrder[displayIndex].Index;
|
||||
|
||||
// Wrap to the next row and start the cell edit
|
||||
adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
|
||||
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a change column command
|
||||
/// </summary>
|
||||
/// <param name="keyData"></param>
|
||||
/// <param name="behaviour"></param>
|
||||
protected virtual void HandleColumnChange(Keys keyData, CellEditCharacterBehaviour behaviour)
|
||||
{
|
||||
// If we couldn't finish editing the current cell, don't try to move it
|
||||
if (!this.ListView.PossibleFinishCellEditing())
|
||||
return;
|
||||
|
||||
// Changing columns only works in details mode
|
||||
if (this.ListView.View != View.Details)
|
||||
return;
|
||||
|
||||
List<OLVColumn> editableColumns = this.EditableColumnsInDisplayOrder;
|
||||
OLVListItem olvi = this.ItemBeingEdited;
|
||||
int displayIndex = Math.Max(0,
|
||||
editableColumns.IndexOf(this.ListView.GetColumn(this.SubItemIndexBeingEdited)));
|
||||
bool isGoingLeft = behaviour == CellEditCharacterBehaviour.ChangeColumnLeft;
|
||||
|
||||
// Are we trying to continue past one of the edges?
|
||||
if ((isGoingLeft && displayIndex == 0) ||
|
||||
(!isGoingLeft && displayIndex == editableColumns.Count - 1))
|
||||
{
|
||||
// Yes, so figure out our at edge behaviour
|
||||
CellEditAtEdgeBehaviour atEdgeBehaviour;
|
||||
if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
|
||||
atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
|
||||
switch (atEdgeBehaviour)
|
||||
{
|
||||
case CellEditAtEdgeBehaviour.Ignore:
|
||||
return;
|
||||
case CellEditAtEdgeBehaviour.EndEdit:
|
||||
this.HandleEndEdit();
|
||||
return;
|
||||
case CellEditAtEdgeBehaviour.ChangeRow:
|
||||
case CellEditAtEdgeBehaviour.Wrap:
|
||||
if (atEdgeBehaviour == CellEditAtEdgeBehaviour.ChangeRow)
|
||||
olvi = GetAdjacentItem(olvi, isGoingLeft && displayIndex == 0);
|
||||
if (isGoingLeft)
|
||||
displayIndex = editableColumns.Count - 1;
|
||||
else
|
||||
displayIndex = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isGoingLeft)
|
||||
displayIndex -= 1;
|
||||
else
|
||||
displayIndex += 1;
|
||||
}
|
||||
|
||||
int subItemIndex = editableColumns[displayIndex].Index;
|
||||
this.StartCellEditIfDifferent(olvi, subItemIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
/// <summary>
|
||||
/// Start editing the indicated cell if that cell is not already being edited
|
||||
/// </summary>
|
||||
/// <param name="olvi">The row to edit</param>
|
||||
/// <param name="subItemIndex">The cell within that row to edit</param>
|
||||
protected void StartCellEditIfDifferent(OLVListItem olvi, int subItemIndex) {
|
||||
if (this.ItemBeingEdited == olvi && this.SubItemIndexBeingEdited == subItemIndex)
|
||||
return;
|
||||
|
||||
this.ListView.EnsureVisible(olvi.Index);
|
||||
this.ListView.StartCellEdit(olvi, subItemIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the adjacent item to the given item in the given direction.
|
||||
/// If that item is disabled, continue in that direction until an enabled item is found.
|
||||
/// </summary>
|
||||
/// <param name="olvi">The row whose neighbour is sought</param>
|
||||
/// <param name="up">The direction of the adjacentness</param>
|
||||
/// <returns>An OLVListView adjacent to the given item, or null if there are no more enabled items in that direction.</returns>
|
||||
protected OLVListItem GetAdjacentItemOrNull(OLVListItem olvi, bool up) {
|
||||
OLVListItem item = up ? this.ListView.GetPreviousItem(olvi) : this.ListView.GetNextItem(olvi);
|
||||
while (item != null && !item.Enabled)
|
||||
item = up ? this.ListView.GetPreviousItem(item) : this.ListView.GetNextItem(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the adjacent item to the given item in the given direction, wrapping if needed.
|
||||
/// </summary>
|
||||
/// <param name="olvi">The row whose neighbour is sought</param>
|
||||
/// <param name="up">The direction of the adjacentness</param>
|
||||
/// <returns>An OLVListView adjacent to the given item, or null if there are no more items in that direction.</returns>
|
||||
protected OLVListItem GetAdjacentItem(OLVListItem olvi, bool up) {
|
||||
return this.GetAdjacentItemOrNull(olvi, up) ?? this.GetAdjacentItemOrNull(null, up);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of columns that are editable in the order they are shown to the user
|
||||
/// </summary>
|
||||
protected List<OLVColumn> EditableColumnsInDisplayOrder {
|
||||
get {
|
||||
List<OLVColumn> editableColumnsInDisplayOrder = new List<OLVColumn>();
|
||||
foreach (OLVColumn x in this.ListView.ColumnsInDisplayOrder)
|
||||
if (x.IsEditable)
|
||||
editableColumnsInDisplayOrder.Add(x);
|
||||
return editableColumnsInDisplayOrder;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
284
ObjectListView/CellEditing/CellEditors.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* CellEditors - Several slightly modified controls that are used as celleditors within ObjectListView.
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 20/10/2008 5:15 PM
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-08-02 JPP - Make most editors public so they can be reused/subclassed
|
||||
* v2.3
|
||||
* 2009-08-13 JPP - Standardized code formatting
|
||||
* v2.2.1
|
||||
* 2008-01-18 JPP - Added special handling for enums
|
||||
* 2008-01-16 JPP - Added EditorRegistry
|
||||
* v2.0.1
|
||||
* 2008-10-20 JPP - Separated from ObjectListView.cs
|
||||
*
|
||||
* Copyright (C) 2006-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// These items allow combo boxes to remember a value and its description.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="description"></param>
|
||||
public class ComboBoxItem(Object key, String description)
|
||||
{
|
||||
private readonly String description = description;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Object Key {
|
||||
get { return key; }
|
||||
}
|
||||
private readonly Object key = key;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override string ToString() {
|
||||
return this.description;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Cell editors
|
||||
// These classes are simple cell editors that make it easier to get and set
|
||||
// the value that the control is showing.
|
||||
// In many cases, you can intercept the CellEditStarting event to
|
||||
// change the characteristics of the editor. For example, changing
|
||||
// the acceptable range for a numeric editor or changing the strings
|
||||
// that respresent true and false values for a boolean editor.
|
||||
|
||||
/// <summary>
|
||||
/// This editor shows and auto completes values from the given listview column.
|
||||
/// </summary>
|
||||
[ToolboxItem(false)]
|
||||
public class AutoCompleteCellEditor : ComboBox
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an AutoCompleteCellEditor
|
||||
/// </summary>
|
||||
/// <param name="lv"></param>
|
||||
/// <param name="column"></param>
|
||||
public AutoCompleteCellEditor(ObjectListView lv, OLVColumn column) {
|
||||
this.DropDownStyle = ComboBoxStyle.DropDown;
|
||||
|
||||
Dictionary<String, bool> alreadySeen = new Dictionary<string, bool>();
|
||||
for (int i = 0; i < Math.Min(lv.GetItemCount(), 1000); i++) {
|
||||
String str = column.GetStringValue(lv.GetModelObject(i));
|
||||
if (!alreadySeen.ContainsKey(str)) {
|
||||
this.Items.Add(str);
|
||||
alreadySeen[str] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.Sorted = true;
|
||||
this.AutoCompleteSource = AutoCompleteSource.ListItems;
|
||||
this.AutoCompleteMode = AutoCompleteMode.Append;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This combo box is specialised to allow editing of an enum.
|
||||
/// </summary>
|
||||
[ToolboxItem(false)]
|
||||
public class EnumCellEditor : ComboBox
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
public EnumCellEditor(Type type) {
|
||||
this.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
this.ValueMember = "Key";
|
||||
|
||||
ArrayList values = new ArrayList();
|
||||
foreach (object value in Enum.GetValues(type))
|
||||
values.Add(new ComboBoxItem(value, Enum.GetName(type, value)));
|
||||
|
||||
this.DataSource = values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This editor simply shows and edits integer values.
|
||||
/// </summary>
|
||||
[ToolboxItem(false)]
|
||||
public class IntUpDown : NumericUpDown
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IntUpDown() {
|
||||
this.DecimalPlaces = 0;
|
||||
this.Minimum = -9999999;
|
||||
this.Maximum = 9999999;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value shown by this editor
|
||||
/// </summary>
|
||||
new public int Value {
|
||||
get { return Decimal.ToInt32(base.Value); }
|
||||
set { base.Value = new Decimal(value); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This editor simply shows and edits unsigned integer values.
|
||||
/// </summary>
|
||||
/// <remarks>This class can't be made public because unsigned int is not a
|
||||
/// CLS-compliant type. If you want to use, just copy the code to this class
|
||||
/// into your project and use it from there.</remarks>
|
||||
[ToolboxItem(false)]
|
||||
internal class UintUpDown : NumericUpDown
|
||||
{
|
||||
public UintUpDown() {
|
||||
this.DecimalPlaces = 0;
|
||||
this.Minimum = 0;
|
||||
this.Maximum = 9999999;
|
||||
}
|
||||
|
||||
new public uint Value {
|
||||
get { return Decimal.ToUInt32(base.Value); }
|
||||
set { base.Value = new Decimal(value); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This editor simply shows and edits boolean values.
|
||||
/// </summary>
|
||||
[ToolboxItem(false)]
|
||||
public class BooleanCellEditor : ComboBox
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public BooleanCellEditor() {
|
||||
this.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
this.ValueMember = "Key";
|
||||
|
||||
ArrayList values = new ArrayList();
|
||||
values.Add(new ComboBoxItem(false, "False"));
|
||||
values.Add(new ComboBoxItem(true, "True"));
|
||||
|
||||
this.DataSource = values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This editor simply shows and edits boolean values using a checkbox
|
||||
/// </summary>
|
||||
[ToolboxItem(false)]
|
||||
public class BooleanCellEditor2 : CheckBox
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value shown by this editor
|
||||
/// </summary>
|
||||
public bool? Value {
|
||||
get {
|
||||
switch (this.CheckState) {
|
||||
case CheckState.Checked: return true;
|
||||
case CheckState.Indeterminate: return null;
|
||||
case CheckState.Unchecked:
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
set {
|
||||
if (value.HasValue)
|
||||
this.CheckState = value.Value ? CheckState.Checked : CheckState.Unchecked;
|
||||
else
|
||||
this.CheckState = CheckState.Indeterminate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the checkbox will be aligned
|
||||
/// </summary>
|
||||
public new HorizontalAlignment TextAlign {
|
||||
get {
|
||||
switch (this.CheckAlign) {
|
||||
case ContentAlignment.MiddleRight: return HorizontalAlignment.Right;
|
||||
case ContentAlignment.MiddleCenter: return HorizontalAlignment.Center;
|
||||
case ContentAlignment.MiddleLeft:
|
||||
default: return HorizontalAlignment.Left;
|
||||
}
|
||||
}
|
||||
set {
|
||||
switch (value) {
|
||||
case HorizontalAlignment.Left:
|
||||
this.CheckAlign = ContentAlignment.MiddleLeft;
|
||||
break;
|
||||
case HorizontalAlignment.Center:
|
||||
this.CheckAlign = ContentAlignment.MiddleCenter;
|
||||
break;
|
||||
case HorizontalAlignment.Right:
|
||||
this.CheckAlign = ContentAlignment.MiddleRight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This editor simply shows and edits floating point values.
|
||||
/// </summary>
|
||||
/// <remarks>You can intercept the CellEditStarting event if you want
|
||||
/// to change the characteristics of the editor. For example, by increasing
|
||||
/// the number of decimal places.</remarks>
|
||||
[ToolboxItem(false)]
|
||||
public class FloatCellEditor : NumericUpDown
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public FloatCellEditor() {
|
||||
this.DecimalPlaces = 2;
|
||||
this.Minimum = -9999999;
|
||||
this.Maximum = 9999999;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value shown by this editor
|
||||
/// </summary>
|
||||
new public double Value {
|
||||
get { return Convert.ToDouble(base.Value); }
|
||||
set { base.Value = Convert.ToDecimal(value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
213
ObjectListView/CellEditing/EditorRegistry.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* EditorRegistry - A registry mapping types to cell editors.
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 6-March-2011 7:53 am
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Use OLVColumn.DataType if the value to be edited is null
|
||||
* 2011-03-06 JPP - Separated from CellEditors.cs
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that creates an editor for the given value
|
||||
/// </summary>
|
||||
/// <param name="model">The model from which that value came</param>
|
||||
/// <param name="column">The column for which the editor is being created</param>
|
||||
/// <param name="value">A representative value of the type to be edited. This value may not be the exact
|
||||
/// value for the column/model combination. It could be simply representative of
|
||||
/// the appropriate type of value.</param>
|
||||
/// <returns>A control which can edit the given value</returns>
|
||||
public delegate Control EditorCreatorDelegate(Object model, OLVColumn column, Object value);
|
||||
|
||||
/// <summary>
|
||||
/// An editor registry gives a way to decide what cell editor should be used to edit
|
||||
/// the value of a cell. Programmers can register non-standard types and the control that
|
||||
/// should be used to edit instances of that type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>All ObjectListViews share the same editor registry.</para>
|
||||
/// </remarks>
|
||||
public class EditorRegistry {
|
||||
#region Initializing
|
||||
|
||||
/// <summary>
|
||||
/// Create an EditorRegistry
|
||||
/// </summary>
|
||||
public EditorRegistry() {
|
||||
this.InitializeStandardTypes();
|
||||
}
|
||||
|
||||
private void InitializeStandardTypes() {
|
||||
this.Register(typeof(Boolean), typeof(BooleanCellEditor));
|
||||
this.Register(typeof(Int16), typeof(IntUpDown));
|
||||
this.Register(typeof(Int32), typeof(IntUpDown));
|
||||
this.Register(typeof(Int64), typeof(IntUpDown));
|
||||
this.Register(typeof(UInt16), typeof(UintUpDown));
|
||||
this.Register(typeof(UInt32), typeof(UintUpDown));
|
||||
this.Register(typeof(UInt64), typeof(UintUpDown));
|
||||
this.Register(typeof(Single), typeof(FloatCellEditor));
|
||||
this.Register(typeof(Double), typeof(FloatCellEditor));
|
||||
this.Register(typeof(DateTime), delegate(Object model, OLVColumn column, Object value) {
|
||||
DateTimePicker c = new DateTimePicker();
|
||||
c.Format = DateTimePickerFormat.Short;
|
||||
return c;
|
||||
});
|
||||
this.Register(typeof(Boolean), delegate(Object model, OLVColumn column, Object value) {
|
||||
CheckBox c = new BooleanCellEditor2();
|
||||
c.ThreeState = column.TriStateCheckBoxes;
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Registering
|
||||
|
||||
/// <summary>
|
||||
/// Register that values of 'type' should be edited by instances of 'controlType'.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of value to be edited</param>
|
||||
/// <param name="controlType">The type of the Control that will edit values of 'type'</param>
|
||||
/// <example>
|
||||
/// ObjectListView.EditorRegistry.Register(typeof(Color), typeof(MySpecialColorEditor));
|
||||
/// </example>
|
||||
public void Register(Type type, Type controlType) {
|
||||
this.Register(type, delegate(Object model, OLVColumn column, Object value) {
|
||||
return controlType.InvokeMember("", BindingFlags.CreateInstance, null, null, null) as Control;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register the given delegate so that it is called to create editors
|
||||
/// for values of the given type
|
||||
/// </summary>
|
||||
/// <param name="type">The type of value to be edited</param>
|
||||
/// <param name="creator">The delegate that will create a control that can edit values of 'type'</param>
|
||||
/// <example>
|
||||
/// ObjectListView.EditorRegistry.Register(typeof(Color), CreateColorEditor);
|
||||
/// ...
|
||||
/// public Control CreateColorEditor(Object model, OLVColumn column, Object value)
|
||||
/// {
|
||||
/// return new MySpecialColorEditor();
|
||||
/// }
|
||||
/// </example>
|
||||
public void Register(Type type, EditorCreatorDelegate creator) {
|
||||
this.creatorMap[type] = creator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a delegate that will be called to create an editor for values
|
||||
/// that have not been handled.
|
||||
/// </summary>
|
||||
/// <param name="creator">The delegate that will create a editor for all other types</param>
|
||||
public void RegisterDefault(EditorCreatorDelegate creator) {
|
||||
this.defaultCreator = creator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a delegate that will be given a chance to create a control
|
||||
/// before any other option is considered.
|
||||
/// </summary>
|
||||
/// <param name="creator">The delegate that will create a control</param>
|
||||
public void RegisterFirstChance(EditorCreatorDelegate creator) {
|
||||
this.firstChanceCreator = creator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the registered handler for the given type
|
||||
/// </summary>
|
||||
/// <remarks>Does nothing if the given type doesn't exist</remarks>
|
||||
/// <param name="type">The type whose registration is to be removed</param>
|
||||
public void Unregister(Type type) {
|
||||
if (this.creatorMap.ContainsKey(type))
|
||||
this.creatorMap.Remove(type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Accessing
|
||||
|
||||
/// <summary>
|
||||
/// Create and return an editor that is appropriate for the given value.
|
||||
/// Return null if no appropriate editor can be found.
|
||||
/// </summary>
|
||||
/// <param name="model">The model involved</param>
|
||||
/// <param name="column">The column to be edited</param>
|
||||
/// <param name="value">The value to be edited. This value may not be the exact
|
||||
/// value for the column/model combination. It could be simply representative of
|
||||
/// the appropriate type of value.</param>
|
||||
/// <returns>A Control that can edit the given type of values</returns>
|
||||
public Control GetEditor(Object model, OLVColumn column, Object value) {
|
||||
Control editor;
|
||||
|
||||
// Give the first chance delegate a chance to decide
|
||||
if (this.firstChanceCreator != null) {
|
||||
editor = this.firstChanceCreator(model, column, value);
|
||||
if (editor != null)
|
||||
return editor;
|
||||
}
|
||||
|
||||
// Try to find a creator based on the type of the value (or the column)
|
||||
Type type = value == null ? column.DataType : value.GetType();
|
||||
if (type != null && this.creatorMap.ContainsKey(type)) {
|
||||
editor = this.creatorMap[type](model, column, value);
|
||||
if (editor != null)
|
||||
return editor;
|
||||
}
|
||||
|
||||
// Enums without other processing get a special editor
|
||||
if (value != null && value.GetType().IsEnum)
|
||||
return this.CreateEnumEditor(value.GetType());
|
||||
|
||||
// Give any default creator a final chance
|
||||
if (this.defaultCreator != null)
|
||||
return this.defaultCreator(model, column, value);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and return an editor that will edit values of the given type
|
||||
/// </summary>
|
||||
/// <param name="type">A enum type</param>
|
||||
protected Control CreateEnumEditor(Type type) {
|
||||
return new EnumCellEditor(type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
private EditorCreatorDelegate firstChanceCreator;
|
||||
private EditorCreatorDelegate defaultCreator;
|
||||
private Dictionary<Type, EditorCreatorDelegate> creatorMap = new Dictionary<Type, EditorCreatorDelegate>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
46
ObjectListView/CustomDictionary.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Dictionary>
|
||||
<Words>
|
||||
<Recognized>
|
||||
<Word>br</Word>
|
||||
<Word>Canceled</Word>
|
||||
<Word>Center</Word>
|
||||
<Word>Color</Word>
|
||||
<Word>Colors</Word>
|
||||
<Word>f</Word>
|
||||
<Word>fmt</Word>
|
||||
<Word>g</Word>
|
||||
<Word>gdi</Word>
|
||||
<Word>hti</Word>
|
||||
<Word>i</Word>
|
||||
<Word>lightbox</Word>
|
||||
<Word>lv</Word>
|
||||
<Word>lvi</Word>
|
||||
<Word>lvsi</Word>
|
||||
<Word>m</Word>
|
||||
<Word>multi</Word>
|
||||
<Word>Munger</Word>
|
||||
<Word>n</Word>
|
||||
<Word>olv</Word>
|
||||
<Word>olvi</Word>
|
||||
<Word>p</Word>
|
||||
<Word>parms</Word>
|
||||
<Word>r</Word>
|
||||
<Word>Renderer</Word>
|
||||
<Word>s</Word>
|
||||
<Word>SubItem</Word>
|
||||
<Word>Unapply</Word>
|
||||
<Word>Unpause</Word>
|
||||
<Word>x</Word>
|
||||
<Word>y</Word>
|
||||
</Recognized>
|
||||
<Deprecated>
|
||||
<Term PreferredAlternate="EnterpriseServices">ComPlus</Term>
|
||||
</Deprecated>
|
||||
</Words>
|
||||
<Acronyms>
|
||||
<CasingExceptions>
|
||||
<Acronym>OLV</Acronym>
|
||||
</CasingExceptions>
|
||||
</Acronyms>
|
||||
</Dictionary>
|
||||
236
ObjectListView/DataListView.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* DataListView - A data-bindable listview
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 27/09/2008 9:15 AM
|
||||
*
|
||||
* Change log:
|
||||
* 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call
|
||||
* v2.6
|
||||
* 2011-02-27 JPP - Moved most of the logic to DataSourceAdapter (where it
|
||||
* can be used by FastDataListView too)
|
||||
* v2.3
|
||||
* 2009-01-18 JPP - Boolean columns are now handled as checkboxes
|
||||
* - Auto-generated columns would fail if the data source was
|
||||
* reseated, even to the same data source
|
||||
* v2.0.1
|
||||
* 2009-01-07 JPP - Made all public and protected methods virtual
|
||||
* 2008-10-03 JPP - Separated from ObjectListView.cs
|
||||
*
|
||||
* Copyright (C) 2006-2015 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Design;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A DataListView is a ListView that can be bound to a datasource (which would normally be a DataTable or DataView).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This listview keeps itself in sync with its source datatable by listening for change events.</para>
|
||||
/// <para>The DataListView will automatically create columns to show all of the data source's columns/properties, if there is not already
|
||||
/// a column showing that property. This allows you to define one or two columns in the designer and then have the others generated automatically.
|
||||
/// If you don't want any column to be auto generated, set <see cref="AutoGenerateColumns"/> to false.
|
||||
/// These generated columns will be only the simplest view of the world, and would look more interesting with a few delegates installed.</para>
|
||||
/// <para>This listview will also automatically generate missing aspect getters to fetch the values from the data view.</para>
|
||||
/// <para>Changing data sources is possible, but error prone. Before changing data sources, the programmer is responsible for modifying/resetting
|
||||
/// the column collection to be valid for the new data source.</para>
|
||||
/// <para>Internally, a CurrencyManager controls keeping the data source in-sync with other users of the data source (as per normal .NET
|
||||
/// behavior). This means that the model objects in the DataListView are DataRowView objects. If you write your own AspectGetters/Setters,
|
||||
/// they will be given DataRowView objects.</para>
|
||||
/// </remarks>
|
||||
public class DataListView : ObjectListView
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Make a DataListView
|
||||
/// </summary>
|
||||
public DataListView()
|
||||
{
|
||||
this.Adapter = new DataSourceAdapter(this);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
this.Adapter.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not columns will be automatically generated to show the
|
||||
/// columns when the DataSource is set.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
|
||||
[Category("Data"),
|
||||
Description("Should the control automatically generate columns from the DataSource"),
|
||||
DefaultValue(true)]
|
||||
public bool AutoGenerateColumns {
|
||||
get { return this.Adapter.AutoGenerateColumns; }
|
||||
set { this.Adapter.AutoGenerateColumns = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the DataSource that will be displayed in this list view.
|
||||
/// </summary>
|
||||
/// <remarks>The DataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
|
||||
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
|
||||
/// <list type="unordered">
|
||||
/// <item><description><see cref="DataView"/></description></item>
|
||||
/// <item><description><see cref="DataTable"/></description></item>
|
||||
/// <item><description><see cref="DataSet"/></description></item>
|
||||
/// <item><description><see cref="DataViewManager"/></description></item>
|
||||
/// <item><description><see cref="BindingSource"/></description></item>
|
||||
/// </list>
|
||||
/// <para>When binding to a list container (i.e. one that implements the
|
||||
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
|
||||
/// you must also set the <see cref="DataMember"/> property in order
|
||||
/// to identify which particular list you would like to display. You
|
||||
/// may also set the <see cref="DataMember"/> property even when
|
||||
/// DataSource refers to a list, since <see cref="DataMember"/> can
|
||||
/// also be used to navigate relations between lists.</para>
|
||||
/// <para>When a DataSource is set, the control will create OLVColumns to show any
|
||||
/// data source columns that are not already shown.</para>
|
||||
/// <para>If the DataSource is changed, you will have to remove any previously
|
||||
/// created columns, since they will be configured for the previous DataSource.
|
||||
/// <see cref="ObjectListView.Reset()"/>.</para>
|
||||
/// </remarks>
|
||||
[Category("Data"),
|
||||
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
|
||||
public virtual Object DataSource
|
||||
{
|
||||
get { return this.Adapter.DataSource; }
|
||||
set { this.Adapter.DataSource = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
|
||||
/// </summary>
|
||||
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
|
||||
[Category("Data"),
|
||||
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
|
||||
DefaultValue("")]
|
||||
public virtual string DataMember
|
||||
{
|
||||
get { return this.Adapter.DataMember; }
|
||||
set { this.Adapter.DataMember = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
|
||||
/// for data binding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Adaptors cannot be shared between controls. Each DataListView needs its own adapter.
|
||||
/// </remarks>
|
||||
protected DataSourceAdapter Adapter {
|
||||
get {
|
||||
Debug.Assert(adapter != null, "Data adapter should not be null");
|
||||
return adapter;
|
||||
}
|
||||
set { adapter = value; }
|
||||
}
|
||||
private DataSourceAdapter adapter;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object manipulations
|
||||
|
||||
/// <summary>
|
||||
/// Add the given collection of model objects to this control.
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">A collection of model objects</param>
|
||||
/// <remarks>This is a no-op for data lists, since the data
|
||||
/// is controlled by the DataSource. Manipulate the data source
|
||||
/// rather than this view of the data source.</remarks>
|
||||
public override void AddObjects(ICollection modelObjects)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert the given collection of objects before the given position
|
||||
/// </summary>
|
||||
/// <param name="index">Where to insert the objects</param>
|
||||
/// <param name="modelObjects">The objects to be inserted</param>
|
||||
/// <remarks>This is a no-op for data lists, since the data
|
||||
/// is controlled by the DataSource. Manipulate the data source
|
||||
/// rather than this view of the data source.</remarks>
|
||||
public override void InsertObjects(int index, ICollection modelObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the given collection of model objects from this control.
|
||||
/// </summary>
|
||||
/// <remarks>This is a no-op for data lists, since the data
|
||||
/// is controlled by the DataSource. Manipulate the data source
|
||||
/// rather than this view of the data source.</remarks>
|
||||
public override void RemoveObjects(ICollection modelObjects)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Change the Unfreeze behaviour
|
||||
/// </summary>
|
||||
protected override void DoUnfreeze() {
|
||||
|
||||
// Copied from base method, but we don't need to BuildList() since we know that our
|
||||
// data adaptor is going to do that immediately after this method exits.
|
||||
this.EndUpdate();
|
||||
this.ResizeFreeSpaceFillingColumns();
|
||||
// this.BuildList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles parent binding context changes
|
||||
/// </summary>
|
||||
/// <param name="e">Unused EventArgs.</param>
|
||||
protected override void OnParentBindingContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnParentBindingContextChanged(e);
|
||||
|
||||
// BindingContext is an ambient property - by default it simply picks
|
||||
// up the parent control's context (unless something has explicitly
|
||||
// given us our own). So we must respond to changes in our parent's
|
||||
// binding context in the same way we would changes to our own
|
||||
// binding context.
|
||||
|
||||
// THINK: Do we need to forward this to the adapter?
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
240
ObjectListView/DataTreeListView.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* DataTreeListView - A data bindable TreeListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 05/05/2012 3:26 PM
|
||||
*
|
||||
* Change log:
|
||||
|
||||
* 2012-05-05 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
|
||||
*
|
||||
* Copyright (C) 2012 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Design;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A DataTreeListView is a TreeListView that calculates its hierarchy based on
|
||||
/// information in the data source.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Like a <see cref="DataListView"/>, a DataTreeListView sources all its information
|
||||
/// from a combination of <see cref="DataSource"/> and <see cref="DataMember"/>.
|
||||
/// <see cref="DataSource"/> can be a DataTable, DataSet,
|
||||
/// or anything that implements <see cref="IList"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To function properly, the DataTreeListView requires:
|
||||
/// <list type="bullet">
|
||||
/// <item>the table to have a column which holds a unique for the row. The name of this column must be set in <see cref="KeyAspectName"/>.</item>
|
||||
/// <item>the table to have a column which holds id of the hierarchical parent of the row. The name of this column must be set in <see cref="ParentKeyAspectName"/>.</item>
|
||||
/// <item>a value which identifies which rows are the roots of the tree (<see cref="RootKeyValue"/>).</item>
|
||||
/// </list>
|
||||
/// The hierarchy structure is determined finding all the rows where the parent key is equal to <see cref="RootKeyValue"/>. These rows
|
||||
/// become the root objects of the hierarchy.
|
||||
/// </para>
|
||||
/// <para>Like a TreeListView, the hierarchy must not contain cycles. Bad things will happen if the data is cyclic.</para>
|
||||
/// </remarks>
|
||||
public partial class DataTreeListView : TreeListView
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not columns will be automatically generated to show the
|
||||
/// columns when the DataSource is set.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
|
||||
[Category("Data"),
|
||||
Description("Should the control automatically generate columns from the DataSource"),
|
||||
DefaultValue(true)]
|
||||
public bool AutoGenerateColumns
|
||||
{
|
||||
get { return this.Adapter.AutoGenerateColumns; }
|
||||
set { this.Adapter.AutoGenerateColumns = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the DataSource that will be displayed in this list view.
|
||||
/// </summary>
|
||||
/// <remarks>The DataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
|
||||
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
|
||||
/// <list type="unordered">
|
||||
/// <item><description><see cref="DataView"/></description></item>
|
||||
/// <item><description><see cref="DataTable"/></description></item>
|
||||
/// <item><description><see cref="DataSet"/></description></item>
|
||||
/// <item><description><see cref="DataViewManager"/></description></item>
|
||||
/// <item><description><see cref="BindingSource"/></description></item>
|
||||
/// </list>
|
||||
/// <para>When binding to a list container (i.e. one that implements the
|
||||
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
|
||||
/// you must also set the <see cref="DataMember"/> property in order
|
||||
/// to identify which particular list you would like to display. You
|
||||
/// may also set the <see cref="DataMember"/> property even when
|
||||
/// DataSource refers to a list, since <see cref="DataMember"/> can
|
||||
/// also be used to navigate relations between lists.</para>
|
||||
/// </remarks>
|
||||
[Category("Data"),
|
||||
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
|
||||
public virtual Object DataSource {
|
||||
get { return this.Adapter.DataSource; }
|
||||
set { this.Adapter.DataSource = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
|
||||
/// </summary>
|
||||
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
|
||||
[Category("Data"),
|
||||
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
|
||||
DefaultValue("")]
|
||||
public virtual string DataMember {
|
||||
get { return this.Adapter.DataMember; }
|
||||
set { this.Adapter.DataMember = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property/column that uniquely identifies each row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value contained by this column must be unique across all rows
|
||||
/// in the data source. Odd and unpredictable things will happen if two
|
||||
/// rows have the same id.
|
||||
/// </para>
|
||||
/// <para>Null cannot be a valid key value.</para>
|
||||
/// </remarks>
|
||||
[Category("Data"),
|
||||
Description("The name of the property/column that holds the key of a row"),
|
||||
DefaultValue(null)]
|
||||
public virtual string KeyAspectName {
|
||||
get { return this.Adapter.KeyAspectName; }
|
||||
set { this.Adapter.KeyAspectName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property/column that contains the key of
|
||||
/// the parent of a row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The test condition for deciding if one row is the parent of another is functionally
|
||||
/// equivilent to this:
|
||||
/// <code>
|
||||
/// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName])
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>Unlike key value, parent keys can be null but a null parent key can only be used
|
||||
/// to identify root objects.</para>
|
||||
/// </remarks>
|
||||
[Category("Data"),
|
||||
Description("The name of the property/column that holds the key of the parent of a row"),
|
||||
DefaultValue(null)]
|
||||
public virtual string ParentKeyAspectName {
|
||||
get { return this.Adapter.ParentKeyAspectName; }
|
||||
set { this.Adapter.ParentKeyAspectName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value that identifies a row as a root object.
|
||||
/// When the ParentKey of a row equals the RootKeyValue, that row will
|
||||
/// be treated as root of the TreeListView.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The test condition for deciding a root object is functionally
|
||||
/// equivilent to this:
|
||||
/// <code>
|
||||
/// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue)
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>The RootKeyValue can be null. Actually, it can be any value that can
|
||||
/// be compared for equality against a basic type.</para>
|
||||
/// <para>If this is set to the wrong value (i.e. to a value that no row
|
||||
/// has in the parent id column), the list will be empty.</para>
|
||||
/// </remarks>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public virtual object RootKeyValue {
|
||||
get { return this.Adapter.RootKeyValue; }
|
||||
set { this.Adapter.RootKeyValue = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value that identifies a row as a root object.
|
||||
/// <see cref="RootKeyValue"/>. The RootKeyValue can be of any type,
|
||||
/// but the IDE cannot sensibly represent a value of any type,
|
||||
/// so this is a typed wrapper around that property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you want the root value to be something other than a string,
|
||||
/// you will have set it yourself.
|
||||
/// </remarks>
|
||||
[Category("Data"),
|
||||
Description("The parent id value that identifies a row as a root object"),
|
||||
DefaultValue(null)]
|
||||
public virtual string RootKeyValueString {
|
||||
get { return Convert.ToString(this.Adapter.RootKeyValue); }
|
||||
set { this.Adapter.RootKeyValue = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not the key columns (id and parent id) should
|
||||
/// be shown to the user.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect
|
||||
/// afterwards.</remarks>
|
||||
[Category("Data"),
|
||||
Description("Should the keys columns (id and parent id) be shown to the user?"),
|
||||
DefaultValue(true)]
|
||||
public virtual bool ShowKeyColumns {
|
||||
get { return this.Adapter.ShowKeyColumns; }
|
||||
set { this.Adapter.ShowKeyColumns = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
|
||||
/// for data binding.
|
||||
/// </summary>
|
||||
protected TreeDataSourceAdapter Adapter {
|
||||
get {
|
||||
if (this.adapter == null)
|
||||
this.adapter = new TreeDataSourceAdapter(this);
|
||||
return adapter;
|
||||
}
|
||||
set { adapter = value; }
|
||||
}
|
||||
private TreeDataSourceAdapter adapter;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
219
ObjectListView/DragDrop/DragSource.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* DragSource.cs - Add drag source functionality to an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 2009-03-17 5:15 PM
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-29 JPP - Separate OLVDataObject.cs
|
||||
* v2.3
|
||||
* 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
|
||||
* (since MS didn't make it part of the 'All' value)
|
||||
* v2.2
|
||||
* 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
|
||||
* 2009-03-17 JPP - Initial version
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// An IDragSource controls how drag out from the ObjectListView will behave
|
||||
/// </summary>
|
||||
public interface IDragSource
|
||||
{
|
||||
/// <summary>
|
||||
/// A drag operation is beginning. Return the data object that will be used
|
||||
/// for data transfer. Return null to prevent the drag from starting. The data
|
||||
/// object will normally include all the selected objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The returned object is later passed to the GetAllowedEffect() and EndDrag()
|
||||
/// methods.
|
||||
/// </remarks>
|
||||
/// <param name="olv">What ObjectListView is being dragged from.</param>
|
||||
/// <param name="button">Which mouse button is down?</param>
|
||||
/// <param name="item">What item was directly dragged by the user? There may be more than just this
|
||||
/// item selected.</param>
|
||||
/// <returns>The data object that will be used for data transfer. This will often be a subclass
|
||||
/// of DataObject, but does not need to be.</returns>
|
||||
Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item);
|
||||
|
||||
/// <summary>
|
||||
/// What operations are possible for this drag? This controls the icon shown during the drag
|
||||
/// </summary>
|
||||
/// <param name="dragObject">The data object returned by StartDrag()</param>
|
||||
/// <returns>A combination of DragDropEffects flags</returns>
|
||||
DragDropEffects GetAllowedEffects(Object dragObject);
|
||||
|
||||
/// <summary>
|
||||
/// The drag operation is complete. Do whatever is necessary to complete the action.
|
||||
/// </summary>
|
||||
/// <param name="dragObject">The data object returned by StartDrag()</param>
|
||||
/// <param name="effect">The value returned from GetAllowedEffects()</param>
|
||||
void EndDrag(Object dragObject, DragDropEffects effect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A do-nothing implementation of IDragSource that can be safely subclassed.
|
||||
/// </summary>
|
||||
public class AbstractDragSource : IDragSource
|
||||
{
|
||||
#region IDragSource Members
|
||||
|
||||
/// <summary>
|
||||
/// See IDragSource documentation
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="button"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See IDragSource documentation
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public virtual DragDropEffects GetAllowedEffects(Object data) {
|
||||
return DragDropEffects.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See IDragSource documentation
|
||||
/// </summary>
|
||||
/// <param name="dragObject"></param>
|
||||
/// <param name="effect"></param>
|
||||
public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reasonable implementation of IDragSource that provides normal
|
||||
/// drag source functionality. It creates a data object that supports
|
||||
/// inter-application dragging of text and HTML representation of
|
||||
/// the dragged rows. It can optionally force a refresh of all dragged
|
||||
/// rows when the drag is complete.
|
||||
/// </summary>
|
||||
/// <remarks>Subclasses can override GetDataObject() to add new
|
||||
/// data formats to the data transfer object.</remarks>
|
||||
public class SimpleDragSource : IDragSource
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a SimpleDragSource
|
||||
/// </summary>
|
||||
public SimpleDragSource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a SimpleDragSource that refreshes the dragged rows when
|
||||
/// the drag is complete
|
||||
/// </summary>
|
||||
/// <param name="refreshAfterDrop"></param>
|
||||
public SimpleDragSource(bool refreshAfterDrop) {
|
||||
this.RefreshAfterDrop = refreshAfterDrop;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the dragged rows should be refreshed when the
|
||||
/// drag operation is complete.
|
||||
/// </summary>
|
||||
public bool RefreshAfterDrop {
|
||||
get { return refreshAfterDrop; }
|
||||
set { refreshAfterDrop = value; }
|
||||
}
|
||||
private bool refreshAfterDrop;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDragSource Members
|
||||
|
||||
/// <summary>
|
||||
/// Create a DataObject when the user does a left mouse drag operation.
|
||||
/// See IDragSource for further information.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="button"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
|
||||
// We only drag on left mouse
|
||||
if (button != MouseButtons.Left)
|
||||
return null;
|
||||
|
||||
return this.CreateDataObject(olv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which operations are allowed in the operation? By default, all operations are supported.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns>All opertions are supported</returns>
|
||||
public virtual DragDropEffects GetAllowedEffects(Object data) {
|
||||
return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'??
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The drag operation is finished. Refreshe the dragged rows if so configured.
|
||||
/// </summary>
|
||||
/// <param name="dragObject"></param>
|
||||
/// <param name="effect"></param>
|
||||
public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
|
||||
OLVDataObject data = dragObject as OLVDataObject;
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (this.RefreshAfterDrop)
|
||||
data.ListView.RefreshObjects(data.ModelObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a data object that will be used to as the data object
|
||||
/// for the drag operation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Subclasses can override this method add new formats to the data object.
|
||||
/// </remarks>
|
||||
/// <param name="olv">The ObjectListView that is the source of the drag</param>
|
||||
/// <returns>A data object for the drag</returns>
|
||||
protected virtual object CreateDataObject(ObjectListView olv) {
|
||||
return new OLVDataObject(olv);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1454
ObjectListView/DragDrop/DropSink.cs
Normal file
185
ObjectListView/DragDrop/OLVDataObject.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* OLVDataObject.cs - An OLE DataObject that knows how to convert rows of an OLV to text and HTML
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 2011-03-29 3:34PM
|
||||
*
|
||||
* Change log:
|
||||
* v2.8
|
||||
* 2014-05-02 JPP - When the listview is completely empty, don't try to set CSV text in the clipboard.
|
||||
* v2.6
|
||||
* 2012-08-08 JPP - Changed to use OLVExporter.
|
||||
* - Added CSV to formats exported to Clipboard
|
||||
* v2.4
|
||||
* 2011-03-29 JPP - Initial version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A data transfer object that knows how to transform a list of model
|
||||
/// objects into a text and HTML representation.
|
||||
/// </summary>
|
||||
public class OLVDataObject : DataObject {
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a data object from the selected objects in the given ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="olv">The source of the data object</param>
|
||||
public OLVDataObject(ObjectListView olv)
|
||||
: this(olv, olv.SelectedObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a data object which operates on the given model objects
|
||||
/// in the given ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="olv">The source of the data object</param>
|
||||
/// <param name="modelObjects">The model objects to be put into the data object</param>
|
||||
public OLVDataObject(ObjectListView olv, IList modelObjects) {
|
||||
this.objectListView = olv;
|
||||
this.modelObjects = modelObjects;
|
||||
this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer;
|
||||
this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy;
|
||||
this.CreateTextFormats();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether hidden columns will also be included in the text
|
||||
/// and HTML representation. If this is false, only visible columns will
|
||||
/// be included.
|
||||
/// </summary>
|
||||
public bool IncludeHiddenColumns {
|
||||
get { return includeHiddenColumns; }
|
||||
}
|
||||
private readonly bool includeHiddenColumns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether column headers will also be included in the text
|
||||
/// and HTML representation.
|
||||
/// </summary>
|
||||
public bool IncludeColumnHeaders {
|
||||
get { return includeColumnHeaders; }
|
||||
}
|
||||
private readonly bool includeColumnHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that is being used as the source of the data
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return objectListView; }
|
||||
}
|
||||
private readonly ObjectListView objectListView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model objects that are to be placed in the data object
|
||||
/// </summary>
|
||||
public IList ModelObjects {
|
||||
get { return modelObjects; }
|
||||
}
|
||||
private readonly IList modelObjects;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Put a text and HTML representation of our model objects
|
||||
/// into the data object.
|
||||
/// </summary>
|
||||
public void CreateTextFormats() {
|
||||
|
||||
OLVExporter exporter = this.CreateExporter();
|
||||
|
||||
// Put both the text and html versions onto the clipboard.
|
||||
// For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
|
||||
// but using SetData() does.
|
||||
//this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
|
||||
this.SetData(exporter.ExportTo(OLVExporter.ExportFormat.TabSeparated));
|
||||
string exportTo = exporter.ExportTo(OLVExporter.ExportFormat.CSV);
|
||||
if (!String.IsNullOrEmpty(exportTo))
|
||||
this.SetText(exportTo, TextDataFormat.CommaSeparatedValue);
|
||||
this.SetText(ConvertToHtmlFragment(exporter.ExportTo(OLVExporter.ExportFormat.HTML)), TextDataFormat.Html);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an exporter for the data contained in this object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected OLVExporter CreateExporter() {
|
||||
OLVExporter exporter = new OLVExporter(this.ListView);
|
||||
exporter.IncludeColumnHeaders = this.IncludeColumnHeaders;
|
||||
exporter.IncludeHiddenColumns = this.IncludeHiddenColumns;
|
||||
exporter.ModelObjects = this.ModelObjects;
|
||||
return exporter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make a HTML representation of our model objects
|
||||
/// </summary>
|
||||
[Obsolete("Use OLVExporter directly instead", false)]
|
||||
public string CreateHtml() {
|
||||
OLVExporter exporter = this.CreateExporter();
|
||||
return exporter.ExportTo(OLVExporter.ExportFormat.HTML);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the fragment of HTML into the Clipboards HTML format.
|
||||
/// </summary>
|
||||
/// <remarks>The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx
|
||||
/// </remarks>
|
||||
/// <param name="fragment">The HTML to put onto the clipboard. It must be valid HTML!</param>
|
||||
/// <returns>A string that can be put onto the clipboard and will be recognized as HTML</returns>
|
||||
private string ConvertToHtmlFragment(string fragment) {
|
||||
// Minimal implementation of HTML clipboard format
|
||||
const string SOURCE = "http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView";
|
||||
|
||||
const String MARKER_BLOCK =
|
||||
"Version:1.0\r\n" +
|
||||
"StartHTML:{0,8}\r\n" +
|
||||
"EndHTML:{1,8}\r\n" +
|
||||
"StartFragment:{2,8}\r\n" +
|
||||
"EndFragment:{3,8}\r\n" +
|
||||
"StartSelection:{2,8}\r\n" +
|
||||
"EndSelection:{3,8}\r\n" +
|
||||
"SourceURL:{4}\r\n" +
|
||||
"{5}";
|
||||
|
||||
int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, SOURCE, "").Length;
|
||||
|
||||
const String DEFAULT_HTML_BODY =
|
||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
|
||||
"<HTML><HEAD></HEAD><BODY><!--StartFragment-->{0}<!--EndFragment--></BODY></HTML>";
|
||||
|
||||
string html = String.Format(DEFAULT_HTML_BODY, fragment);
|
||||
int startFragment = prefixLength + html.IndexOf(fragment, StringComparison.Ordinal);
|
||||
int endFragment = startFragment + fragment.Length;
|
||||
|
||||
return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, SOURCE, html);
|
||||
}
|
||||
}
|
||||
}
|
||||
165
ObjectListView/FastDataListView.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* FastDataListView - A data bindable listview that has the speed of a virtual list
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 22/09/2010 8:11 AM
|
||||
*
|
||||
* Change log:
|
||||
* 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call
|
||||
* v2.6
|
||||
* 2010-09-22 JPP - Initial version
|
||||
*
|
||||
* Copyright (C) 2006-2015 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing.Design;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A FastDataListView virtualizes the display of data from a DataSource. It operates on
|
||||
/// DataSets and DataTables in the same way as a DataListView, but does so much more efficiently.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A FastDataListView still has to load all its data from the DataSource. If you have SQL statement
|
||||
/// that returns 1 million rows, all 1 million rows will still need to read from the database.
|
||||
/// However, once the rows are loaded, the FastDataListView will only build rows as they are displayed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class FastDataListView : FastObjectListView
|
||||
{
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (this.adapter != null) {
|
||||
this.adapter.Dispose();
|
||||
this.adapter = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not columns will be automatically generated to show the
|
||||
/// columns when the DataSource is set.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
|
||||
[Category("Data"),
|
||||
Description("Should the control automatically generate columns from the DataSource"),
|
||||
DefaultValue(true)]
|
||||
public bool AutoGenerateColumns
|
||||
{
|
||||
get { return this.Adapter.AutoGenerateColumns; }
|
||||
set { this.Adapter.AutoGenerateColumns = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the VirtualListDataSource that will be displayed in this list view.
|
||||
/// </summary>
|
||||
/// <remarks>The VirtualListDataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
|
||||
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
|
||||
/// <list type="unordered">
|
||||
/// <item><description><see cref="DataView"/></description></item>
|
||||
/// <item><description><see cref="DataTable"/></description></item>
|
||||
/// <item><description><see cref="DataSet"/></description></item>
|
||||
/// <item><description><see cref="DataViewManager"/></description></item>
|
||||
/// <item><description><see cref="BindingSource"/></description></item>
|
||||
/// </list>
|
||||
/// <para>When binding to a list container (i.e. one that implements the
|
||||
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
|
||||
/// you must also set the <see cref="DataMember"/> property in order
|
||||
/// to identify which particular list you would like to display. You
|
||||
/// may also set the <see cref="DataMember"/> property even when
|
||||
/// VirtualListDataSource refers to a list, since <see cref="DataMember"/> can
|
||||
/// also be used to navigate relations between lists.</para>
|
||||
/// </remarks>
|
||||
[Category("Data"),
|
||||
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
|
||||
public virtual Object DataSource {
|
||||
get { return this.Adapter.DataSource; }
|
||||
set { this.Adapter.DataSource = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
|
||||
/// </summary>
|
||||
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
|
||||
[Category("Data"),
|
||||
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
|
||||
DefaultValue("")]
|
||||
public virtual string DataMember {
|
||||
get { return this.Adapter.DataMember; }
|
||||
set { this.Adapter.DataMember = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
|
||||
/// for data binding.
|
||||
/// </summary>
|
||||
protected DataSourceAdapter Adapter {
|
||||
get {
|
||||
if (adapter == null)
|
||||
adapter = this.CreateDataSourceAdapter();
|
||||
return adapter;
|
||||
}
|
||||
set { adapter = value; }
|
||||
}
|
||||
private DataSourceAdapter adapter;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Create the DataSourceAdapter that this control will use.
|
||||
/// </summary>
|
||||
/// <returns>A DataSourceAdapter configured for this list</returns>
|
||||
/// <remarks>Subclasses should override this to create their
|
||||
/// own specialized adapters</remarks>
|
||||
protected virtual DataSourceAdapter CreateDataSourceAdapter() {
|
||||
return new DataSourceAdapter(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the Unfreeze behaviour
|
||||
/// </summary>
|
||||
protected override void DoUnfreeze()
|
||||
{
|
||||
|
||||
// Copied from base method, but we don't need to BuildList() since we know that our
|
||||
// data adaptor is going to do that immediately after this method exits.
|
||||
this.EndUpdate();
|
||||
this.ResizeFreeSpaceFillingColumns();
|
||||
// this.BuildList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
419
ObjectListView/FastObjectListView.cs
Normal file
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* FastObjectListView - A listview that behaves like an ObjectListView but has the speed of a virtual list
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 27/09/2008 9:15 AM
|
||||
*
|
||||
* Change log:
|
||||
* 2014-10-15 JPP - Fire Filter event when applying filters
|
||||
* v2.8
|
||||
* 2012-06-11 JPP - Added more efficient version of FilteredObjects
|
||||
* v2.5.1
|
||||
* 2011-04-25 JPP - Fixed problem with removing objects from filtered or sorted list
|
||||
* v2.4
|
||||
* 2010-04-05 JPP - Added filtering
|
||||
* v2.3
|
||||
* 2009-08-27 JPP - Added GroupingStrategy
|
||||
* - Added optimized Objects property
|
||||
* v2.2.1
|
||||
* 2009-01-07 JPP - Made all public and protected methods virtual
|
||||
* 2008-09-27 JPP - Separated from ObjectListView.cs
|
||||
*
|
||||
* Copyright (C) 2006-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A FastObjectListView trades function for speed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>On my mid-range laptop, this view builds a list of 10,000 objects in 0.1 seconds,
|
||||
/// as opposed to a normal ObjectListView which takes 10-15 seconds. Lists of up to 50,000 items should be
|
||||
/// able to be handled with sub-second response times even on low end machines.</para>
|
||||
/// <para>
|
||||
/// A FastObjectListView is implemented as a virtual list with many of the virtual modes limits (e.g. no sorting)
|
||||
/// fixed through coding. There are some functions that simply cannot be provided. Specifically, a FastObjectListView cannot:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>use Tile view</description></item>
|
||||
/// <item><description>show groups on XP</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class FastObjectListView : VirtualObjectListView
|
||||
{
|
||||
/// <summary>
|
||||
/// Make a FastObjectListView
|
||||
/// </summary>
|
||||
public FastObjectListView() {
|
||||
this.VirtualListDataSource = new FastObjectListDataSource(this);
|
||||
this.GroupingStrategy = new FastListGroupingStrategy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of objects that survive any filtering that may be in place.
|
||||
/// </summary>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public override IEnumerable FilteredObjects {
|
||||
get {
|
||||
// This is much faster than the base method
|
||||
return ((FastObjectListDataSource)this.VirtualListDataSource).FilteredObjectList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set the collection of objects that this list will show
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The contents of the control will be updated immediately after setting this property.
|
||||
/// </para>
|
||||
/// <para>This method preserves selection, if possible. Use SetObjects() if
|
||||
/// you do not want to preserve the selection. Preserving selection is the slowest part of this
|
||||
/// code and performance is O(n) where n is the number of selected rows.</para>
|
||||
/// <para>This method is not thread safe.</para>
|
||||
/// </remarks>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public override IEnumerable Objects {
|
||||
get {
|
||||
// This is much faster than the base method
|
||||
return ((FastObjectListDataSource)this.VirtualListDataSource).ObjectList;
|
||||
}
|
||||
set { base.Objects = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the given collection of objects to the given index.
|
||||
/// </summary>
|
||||
/// <remarks>This operation only makes sense on non-grouped ObjectListViews.</remarks>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObjects"></param>
|
||||
public override void MoveObjects(int index, ICollection modelObjects) {
|
||||
if (this.InvokeRequired) {
|
||||
this.Invoke((MethodInvoker)delegate() { this.MoveObjects(index, modelObjects); });
|
||||
return;
|
||||
}
|
||||
|
||||
// If any object that is going to be moved is before the point where the insertion
|
||||
// will occur, then we have to reduce the location of our insertion point
|
||||
int displacedObjectCount = 0;
|
||||
foreach (object modelObject in modelObjects) {
|
||||
int i = this.IndexOf(modelObject);
|
||||
if (i >= 0 && i <= index)
|
||||
displacedObjectCount++;
|
||||
}
|
||||
index -= displacedObjectCount;
|
||||
|
||||
this.BeginUpdate();
|
||||
try {
|
||||
this.RemoveObjects(modelObjects);
|
||||
this.InsertObjects(index, modelObjects);
|
||||
}
|
||||
finally {
|
||||
this.EndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove any sorting and revert to the given order of the model objects
|
||||
/// </summary>
|
||||
/// <remarks>To be really honest, Unsort() doesn't work on FastObjectListViews since
|
||||
/// the original ordering of model objects is lost when Sort() is called. So this method
|
||||
/// effectively just turns off sorting.</remarks>
|
||||
public override void Unsort() {
|
||||
this.ShowGroups = false;
|
||||
this.PrimarySortColumn = null;
|
||||
this.PrimarySortOrder = SortOrder.None;
|
||||
this.SetObjects(this.Objects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide a data source for a FastObjectListView
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class isn't intended to be used directly, but it is left as a public
|
||||
/// class just in case someone wants to subclass it.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Create a FastObjectListDataSource
|
||||
/// </remarks>
|
||||
/// <param name="listView"></param>
|
||||
public class FastObjectListDataSource(FastObjectListView listView) : AbstractVirtualListDataSource(listView)
|
||||
{
|
||||
|
||||
#region IVirtualListDataSource Members
|
||||
|
||||
/// <summary>
|
||||
/// Get n'th object
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
public override object GetNthObject(int n) {
|
||||
if (n >= 0 && n < this.filteredObjectList.Count)
|
||||
return this.filteredObjectList[n];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many items are in the data source
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetObjectCount() {
|
||||
return this.filteredObjectList.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the given model
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetObjectIndex(object model) {
|
||||
int index;
|
||||
|
||||
if (model != null && this.objectsToIndexMap.TryGetValue(model, out index))
|
||||
return index;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public override int SearchText(string text, int first, int last, OLVColumn column) {
|
||||
if (first <= last) {
|
||||
for (int i = first; i <= last; i++) {
|
||||
string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
|
||||
if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
for (int i = first; i >= last; i--) {
|
||||
string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
|
||||
if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="sortOrder"></param>
|
||||
public override void Sort(OLVColumn column, SortOrder sortOrder) {
|
||||
if (sortOrder != SortOrder.None) {
|
||||
ModelObjectComparer comparer = new ModelObjectComparer(column, sortOrder, this.listView.SecondarySortColumn, this.listView.SecondarySortOrder);
|
||||
this.fullObjectList.Sort(comparer);
|
||||
this.filteredObjectList.Sort(comparer);
|
||||
}
|
||||
this.RebuildIndexMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
public override void AddObjects(ICollection modelObjects) {
|
||||
foreach (object modelObject in modelObjects) {
|
||||
if (modelObject != null)
|
||||
this.fullObjectList.Add(modelObject);
|
||||
}
|
||||
this.FilterObjects();
|
||||
this.RebuildIndexMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObjects"></param>
|
||||
public override void InsertObjects(int index, ICollection modelObjects) {
|
||||
this.fullObjectList.InsertRange(index, modelObjects);
|
||||
this.FilterObjects();
|
||||
this.RebuildIndexMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the given collection of models from this source.
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
public override void RemoveObjects(ICollection modelObjects) {
|
||||
|
||||
// We have to unselect any object that is about to be deleted
|
||||
List<int> indicesToRemove = new List<int>();
|
||||
foreach (object modelObject in modelObjects) {
|
||||
int i = this.GetObjectIndex(modelObject);
|
||||
if (i >= 0)
|
||||
indicesToRemove.Add(i);
|
||||
}
|
||||
|
||||
// Sort the indices from highest to lowest so that we
|
||||
// remove latter ones before earlier ones. In this way, the
|
||||
// indices of the rows doesn't change after the deletes.
|
||||
indicesToRemove.Sort();
|
||||
indicesToRemove.Reverse();
|
||||
|
||||
foreach (int i in indicesToRemove)
|
||||
this.listView.SelectedIndices.Remove(i);
|
||||
|
||||
// Remove the objects from the unfiltered list
|
||||
foreach (object modelObject in modelObjects)
|
||||
this.fullObjectList.Remove(modelObject);
|
||||
|
||||
this.FilterObjects();
|
||||
this.RebuildIndexMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public override void SetObjects(IEnumerable collection) {
|
||||
ArrayList newObjects = ObjectListView.EnumerableToArray(collection, true);
|
||||
|
||||
this.fullObjectList = newObjects;
|
||||
this.FilterObjects();
|
||||
this.RebuildIndexMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update/replace the nth object with the given object
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObject"></param>
|
||||
public override void UpdateObject(int index, object modelObject) {
|
||||
if (index < 0 || index >= this.filteredObjectList.Count)
|
||||
return;
|
||||
|
||||
int i = this.fullObjectList.IndexOf(this.filteredObjectList[index]);
|
||||
if (i < 0)
|
||||
return;
|
||||
|
||||
if (ReferenceEquals(this.fullObjectList[i], modelObject))
|
||||
return;
|
||||
|
||||
this.fullObjectList[i] = modelObject;
|
||||
this.filteredObjectList[index] = modelObject;
|
||||
this.objectsToIndexMap[modelObject] = index;
|
||||
}
|
||||
|
||||
private ArrayList fullObjectList = new ArrayList();
|
||||
private ArrayList filteredObjectList = new ArrayList();
|
||||
private IModelFilter modelFilter;
|
||||
private IListFilter listFilter;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFilterableDataSource Members
|
||||
|
||||
/// <summary>
|
||||
/// Apply the given filters to this data source. One or both may be null.
|
||||
/// </summary>
|
||||
/// <param name="iModelFilter"></param>
|
||||
/// <param name="iListFilter"></param>
|
||||
public override void ApplyFilters(IModelFilter iModelFilter, IListFilter iListFilter) {
|
||||
this.modelFilter = iModelFilter;
|
||||
this.listFilter = iListFilter;
|
||||
this.SetObjects(this.fullObjectList);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full list of objects being used for this fast list.
|
||||
/// This list is unfiltered.
|
||||
/// </summary>
|
||||
public ArrayList ObjectList {
|
||||
get { return fullObjectList; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of objects from ObjectList which survive any installed filters.
|
||||
/// </summary>
|
||||
public ArrayList FilteredObjectList {
|
||||
get { return filteredObjectList; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild the map that remembers which model object is displayed at which line
|
||||
/// </summary>
|
||||
protected void RebuildIndexMap() {
|
||||
this.objectsToIndexMap.Clear();
|
||||
for (int i = 0; i < this.filteredObjectList.Count; i++)
|
||||
this.objectsToIndexMap[this.filteredObjectList[i]] = i;
|
||||
}
|
||||
readonly Dictionary<Object, int> objectsToIndexMap = new Dictionary<Object, int>();
|
||||
|
||||
/// <summary>
|
||||
/// Build our filtered list from our full list.
|
||||
/// </summary>
|
||||
protected void FilterObjects() {
|
||||
|
||||
// If this list isn't filtered, we don't need to do anything else
|
||||
if (!this.listView.UseFiltering) {
|
||||
this.filteredObjectList = new ArrayList(this.fullObjectList);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell the world to filter the objects. If they do so, don't do anything else
|
||||
// ReSharper disable PossibleMultipleEnumeration
|
||||
FilterEventArgs args = new FilterEventArgs(this.fullObjectList);
|
||||
this.listView.OnFilter(args);
|
||||
if (args.FilteredObjects != null) {
|
||||
this.filteredObjectList = ObjectListView.EnumerableToArray(args.FilteredObjects, false);
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable objects = (this.listFilter == null) ?
|
||||
this.fullObjectList : this.listFilter.Filter(this.fullObjectList);
|
||||
|
||||
// Apply the object filter if there is one
|
||||
if (this.modelFilter == null) {
|
||||
this.filteredObjectList = ObjectListView.EnumerableToArray(objects, false);
|
||||
} else {
|
||||
this.filteredObjectList = new ArrayList();
|
||||
foreach (object model in objects) {
|
||||
if (this.modelFilter.Filter(model))
|
||||
this.filteredObjectList.Add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
125
ObjectListView/Filtering/Cluster.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Cluster - Implements a simple cluster
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 3-March-2011 10:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-03 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// Concrete implementation of the ICluster interface.
|
||||
/// </summary>
|
||||
public class Cluster : ICluster {
|
||||
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a cluster
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the cluster</param>
|
||||
public Cluster(object key) {
|
||||
this.Count = 1;
|
||||
this.ClusterKey = key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public overrides
|
||||
|
||||
/// <summary>
|
||||
/// Return a string representation of this cluster
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString() {
|
||||
return this.DisplayLabel ?? "[empty]";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICluster
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how many items belong to this cluster
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return count; }
|
||||
set { count = value; }
|
||||
}
|
||||
private int count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label that will be shown to the user to represent
|
||||
/// this cluster
|
||||
/// </summary>
|
||||
public string DisplayLabel {
|
||||
get { return displayLabel; }
|
||||
set { displayLabel = value; }
|
||||
}
|
||||
private string displayLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual data object that all members of this cluster
|
||||
/// have commonly returned.
|
||||
/// </summary>
|
||||
public object ClusterKey {
|
||||
get { return clusterKey; }
|
||||
set { clusterKey = value; }
|
||||
}
|
||||
private object clusterKey;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IComparable
|
||||
|
||||
/// <summary>
|
||||
/// Return an indication of the ordering between this object and the given one
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
/// <returns></returns>
|
||||
public int CompareTo(object other) {
|
||||
if (other == null || other == System.DBNull.Value)
|
||||
return 1;
|
||||
|
||||
ICluster otherCluster = other as ICluster;
|
||||
if (otherCluster == null)
|
||||
return 1;
|
||||
|
||||
string keyAsString = this.ClusterKey as string;
|
||||
if (keyAsString != null)
|
||||
return String.Compare(keyAsString, otherCluster.ClusterKey as string, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
IComparable keyAsComparable = this.ClusterKey as IComparable;
|
||||
if (keyAsComparable != null)
|
||||
return keyAsComparable.CompareTo(otherCluster.ClusterKey);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
189
ObjectListView/Filtering/ClusteringStrategy.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ClusteringStrategy - Implements a simple clustering strategy
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 3-March-2011 10:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-03 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// This class provides a useful base implemention of a clustering
|
||||
/// strategy where the clusters are grouped around the value of a given column.
|
||||
/// </summary>
|
||||
public class ClusteringStrategy : IClusteringStrategy {
|
||||
|
||||
#region Static properties
|
||||
|
||||
/// <summary>
|
||||
/// This field is the text that will be shown to the user when a cluster
|
||||
/// key is null. It is exposed so it can be localized.
|
||||
/// </summary>
|
||||
static public string NULL_LABEL = "[null]";
|
||||
|
||||
/// <summary>
|
||||
/// This field is the text that will be shown to the user when a cluster
|
||||
/// key is empty (i.e. a string of zero length). It is exposed so it can be localized.
|
||||
/// </summary>
|
||||
static public string EMPTY_LABEL = "[empty]";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format that will be used by default for clusters that only
|
||||
/// contain 1 item. The format string must accept two placeholders:
|
||||
/// - {0} is the cluster key converted to a string
|
||||
/// - {1} is the number of items in the cluster (always 1 in this case)
|
||||
/// </summary>
|
||||
static public string DefaultDisplayLabelFormatSingular {
|
||||
get { return defaultDisplayLabelFormatSingular; }
|
||||
set { defaultDisplayLabelFormatSingular = value; }
|
||||
}
|
||||
static private string defaultDisplayLabelFormatSingular = "{0} ({1} item)";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format that will be used by default for clusters that
|
||||
/// contain 0 or two or more items. The format string must accept two placeholders:
|
||||
/// - {0} is the cluster key converted to a string
|
||||
/// - {1} is the number of items in the cluster
|
||||
/// </summary>
|
||||
static public string DefaultDisplayLabelFormatPlural {
|
||||
get { return defaultDisplayLabelFormatPural; }
|
||||
set { defaultDisplayLabelFormatPural = value; }
|
||||
}
|
||||
static private string defaultDisplayLabelFormatPural = "{0} ({1} items)";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a clustering strategy
|
||||
/// </summary>
|
||||
public ClusteringStrategy() {
|
||||
this.DisplayLabelFormatSingular = DefaultDisplayLabelFormatSingular;
|
||||
this.DisplayLabelFormatPlural = DefaultDisplayLabelFormatPlural;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column upon which this strategy is operating
|
||||
/// </summary>
|
||||
public OLVColumn Column {
|
||||
get { return column; }
|
||||
set { column = value; }
|
||||
}
|
||||
private OLVColumn column;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format that will be used when the cluster
|
||||
/// contains only 1 item. The format string must accept two placeholders:
|
||||
/// - {0} is the cluster key converted to a string
|
||||
/// - {1} is the number of items in the cluster (always 1 in this case)
|
||||
/// </summary>
|
||||
/// <remarks>If this is not set, the value from
|
||||
/// ClusteringStrategy.DefaultDisplayLabelFormatSingular will be used</remarks>
|
||||
public string DisplayLabelFormatSingular {
|
||||
get { return displayLabelFormatSingular; }
|
||||
set { displayLabelFormatSingular = value; }
|
||||
}
|
||||
private string displayLabelFormatSingular;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format that will be used when the cluster
|
||||
/// contains 0 or two or more items. The format string must accept two placeholders:
|
||||
/// - {0} is the cluster key converted to a string
|
||||
/// - {1} is the number of items in the cluster
|
||||
/// </summary>
|
||||
/// <remarks>If this is not set, the value from
|
||||
/// ClusteringStrategy.DefaultDisplayLabelFormatPlural will be used</remarks>
|
||||
public string DisplayLabelFormatPlural {
|
||||
get { return displayLabelFormatPural; }
|
||||
set { displayLabelFormatPural = value; }
|
||||
}
|
||||
private string displayLabelFormatPural;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICluster implementation
|
||||
|
||||
/// <summary>
|
||||
/// Get the cluster key by which the given model will be partitioned by this strategy
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
virtual public object GetClusterKey(object model) {
|
||||
return this.Column.GetValue(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a cluster to hold the given cluster key
|
||||
/// </summary>
|
||||
/// <param name="clusterKey"></param>
|
||||
/// <returns></returns>
|
||||
virtual public ICluster CreateCluster(object clusterKey) {
|
||||
return new Cluster(clusterKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display label that the given cluster should use
|
||||
/// </summary>
|
||||
/// <param name="cluster"></param>
|
||||
/// <returns></returns>
|
||||
virtual public string GetClusterDisplayLabel(ICluster cluster) {
|
||||
string s = this.Column.ValueToString(cluster.ClusterKey) ?? NULL_LABEL;
|
||||
if (String.IsNullOrEmpty(s))
|
||||
s = EMPTY_LABEL;
|
||||
return this.ApplyDisplayFormat(cluster, s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter that will include only model objects that
|
||||
/// match one or more of the given values.
|
||||
/// </summary>
|
||||
/// <param name="valuesChosenForFiltering"></param>
|
||||
/// <returns></returns>
|
||||
virtual public IModelFilter CreateFilter(IList valuesChosenForFiltering) {
|
||||
return new OneOfFilter(this.GetClusterKey, valuesChosenForFiltering);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a label that combines the string representation of the cluster
|
||||
/// key with a format string that holds an "X [N items in cluster]" type layout.
|
||||
/// </summary>
|
||||
/// <param name="cluster"></param>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
virtual protected string ApplyDisplayFormat(ICluster cluster, string s) {
|
||||
string format = (cluster.Count == 1) ? this.DisplayLabelFormatSingular : this.DisplayLabelFormatPlural;
|
||||
return String.IsNullOrEmpty(format) ? s : String.Format(format, s, cluster.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
70
ObjectListView/Filtering/ClustersFromGroupsStrategy.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* ClusteringStrategy - Implements a simple clustering strategy
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 1-April-2011 8:12am
|
||||
*
|
||||
* Change log:
|
||||
* 2011-04-01 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// This class calculates clusters from the groups that the column uses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is the default strategy for all non-date, filterable columns.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This class does not strictly mimic the groups created by the given column.
|
||||
/// In particular, if the programmer changes the default grouping technique
|
||||
/// by listening for grouping events, this class will not mimic that behaviour.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class ClustersFromGroupsStrategy : ClusteringStrategy {
|
||||
|
||||
/// <summary>
|
||||
/// Get the cluster key by which the given model will be partitioned by this strategy
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public override object GetClusterKey(object model) {
|
||||
return this.Column.GetGroupKey(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display label that the given cluster should use
|
||||
/// </summary>
|
||||
/// <param name="cluster"></param>
|
||||
/// <returns></returns>
|
||||
public override string GetClusterDisplayLabel(ICluster cluster) {
|
||||
string s = this.Column.ConvertGroupKeyToTitle(cluster.ClusterKey);
|
||||
if (String.IsNullOrEmpty(s))
|
||||
s = EMPTY_LABEL;
|
||||
return this.ApplyDisplayFormat(cluster, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
187
ObjectListView/Filtering/DateTimeClusteringStrategy.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* DateTimeClusteringStrategy - A strategy to cluster objects by a date time
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 30-March-2011 9:40am
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-30 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// This enum is used to indicate various portions of a datetime
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DateTimePortion {
|
||||
/// <summary>
|
||||
/// Year
|
||||
/// </summary>
|
||||
Year = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Month
|
||||
/// </summary>
|
||||
Month = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Day of the month
|
||||
/// </summary>
|
||||
Day = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Hour
|
||||
/// </summary>
|
||||
Hour = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Minute
|
||||
/// </summary>
|
||||
Minute = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Second
|
||||
/// </summary>
|
||||
Second = 0x20
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class implements a strategy where the model objects are clustered
|
||||
/// according to some portion of the datetime value in the configured column.
|
||||
/// </summary>
|
||||
/// <remarks>To create a strategy that grouped people who were born in
|
||||
/// the same month, you would create a strategy that extracted just
|
||||
/// the month, and formatted it to show just the month's name. Like this:
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// someColumn.ClusteringStrategy = new DateTimeClusteringStrategy(DateTimePortion.Month, "MMMM");
|
||||
/// </example>
|
||||
public class DateTimeClusteringStrategy : ClusteringStrategy {
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a strategy that clusters by month/year
|
||||
/// </summary>
|
||||
public DateTimeClusteringStrategy()
|
||||
: this(DateTimePortion.Year | DateTimePortion.Month, "MMMM yyyy") {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a strategy that clusters around the given parts
|
||||
/// </summary>
|
||||
/// <param name="portions"></param>
|
||||
/// <param name="format"></param>
|
||||
public DateTimeClusteringStrategy(DateTimePortion portions, string format) {
|
||||
this.Portions = portions;
|
||||
this.Format = format;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format string will will be used to create a user-presentable
|
||||
/// version of the cluster key.
|
||||
/// </summary>
|
||||
/// <remarks>The format should use the date/time format strings, as documented
|
||||
/// in the Windows SDK. Both standard formats and custom format will work.</remarks>
|
||||
/// <example>"D" - long date pattern</example>
|
||||
/// <example>"MMMM, yyyy" - "January, 1999"</example>
|
||||
public string Format {
|
||||
get { return format; }
|
||||
set { format = value; }
|
||||
}
|
||||
private string format;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parts of the DateTime that will be extracted when
|
||||
/// determining the clustering key for an object.
|
||||
/// </summary>
|
||||
public DateTimePortion Portions {
|
||||
get { return portions; }
|
||||
set { portions = value; }
|
||||
}
|
||||
private DateTimePortion portions = DateTimePortion.Year | DateTimePortion.Month;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClusterStrategy implementation
|
||||
|
||||
/// <summary>
|
||||
/// Get the cluster key by which the given model will be partitioned by this strategy
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public override object GetClusterKey(object model) {
|
||||
// Get the data attribute we want from the given model
|
||||
// Make sure the returned value is a DateTime
|
||||
DateTime? dateTime = this.Column.GetValue(model) as DateTime?;
|
||||
if (!dateTime.HasValue)
|
||||
return null;
|
||||
|
||||
// Extract the parts of the datetime that we are intereted in.
|
||||
// Even if we aren't interested in a particular portion, we still have to give it a reasonable default
|
||||
// otherwise we won't be able to build a DateTime object for it
|
||||
int year = ((this.Portions & DateTimePortion.Year) == DateTimePortion.Year) ? dateTime.Value.Year : 1;
|
||||
int month = ((this.Portions & DateTimePortion.Month) == DateTimePortion.Month) ? dateTime.Value.Month : 1;
|
||||
int day = ((this.Portions & DateTimePortion.Day) == DateTimePortion.Day) ? dateTime.Value.Day : 1;
|
||||
int hour = ((this.Portions & DateTimePortion.Hour) == DateTimePortion.Hour) ? dateTime.Value.Hour : 0;
|
||||
int minute = ((this.Portions & DateTimePortion.Minute) == DateTimePortion.Minute) ? dateTime.Value.Minute : 0;
|
||||
int second = ((this.Portions & DateTimePortion.Second) == DateTimePortion.Second) ? dateTime.Value.Second : 0;
|
||||
|
||||
return new DateTime(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display label that the given cluster should use
|
||||
/// </summary>
|
||||
/// <param name="cluster"></param>
|
||||
/// <returns></returns>
|
||||
public override string GetClusterDisplayLabel(ICluster cluster) {
|
||||
DateTime? dateTime = cluster.ClusterKey as DateTime?;
|
||||
|
||||
return this.ApplyDisplayFormat(cluster, dateTime.HasValue ? this.DateToString(dateTime.Value) : NULL_LABEL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the given date into a user presentable string
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual string DateToString(DateTime dateTime) {
|
||||
if (String.IsNullOrEmpty(this.Format))
|
||||
return dateTime.ToString(CultureInfo.CurrentUICulture);
|
||||
|
||||
try {
|
||||
return dateTime.ToString(this.Format);
|
||||
}
|
||||
catch (FormatException) {
|
||||
return String.Format("Bad format string '{0}' for value '{1}'", this.Format, dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
369
ObjectListView/Filtering/FilterMenuBuilder.cs
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* FilterMenuBuilder - Responsible for creating a Filter menu
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 4-March-2011 11:59 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2012-05-20 JPP - Allow the same model object to be in multiple clusters
|
||||
* Useful for xor'ed flag fields, and multi-value strings
|
||||
* (e.g. hobbies that are stored as comma separated values).
|
||||
* v2.5.1
|
||||
* 2012-04-14 JPP - Fixed rare bug with clustering an empty list (SF #3445118)
|
||||
* v2.5
|
||||
* 2011-04-12 JPP - Added some images to menu
|
||||
* 2011-03-04 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Collections;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class know how to build a Filter menu.
|
||||
/// It is responsible for clustering the values in the target column,
|
||||
/// build a menu that shows those clusters, and then constructing
|
||||
/// a filter that will enact the users choices.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Almost all of the methods in this class are declared as "virtual protected"
|
||||
/// so that subclasses can provide alternative behaviours.
|
||||
/// </remarks>
|
||||
public class FilterMenuBuilder {
|
||||
|
||||
#region Static properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string that labels the Apply button.
|
||||
/// Exposed so it can be localized.
|
||||
/// </summary>
|
||||
static public string APPLY_LABEL = "Apply";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string that labels the Clear All menu item.
|
||||
/// Exposed so it can be localized.
|
||||
/// </summary>
|
||||
static public string CLEAR_ALL_FILTERS_LABEL = "Clear All Filters";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string that labels the Filtering menu as a whole..
|
||||
/// Exposed so it can be localized.
|
||||
/// </summary>
|
||||
static public string FILTERING_LABEL = "Filtering";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string that represents Select All values.
|
||||
/// If this is set to null or empty, no Select All option will be included.
|
||||
/// Exposed so it can be localized.
|
||||
/// </summary>
|
||||
static public string SELECT_ALL_LABEL = "Select All";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image that will be placed next to the Clear Filtering menu item
|
||||
/// </summary>
|
||||
static public Bitmap ClearFilteringImage = BrightIdeasSoftware.Properties.Resources.ClearFiltering;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image that will be placed next to all "Apply" menu items on the filtering menu
|
||||
/// </summary>
|
||||
static public Bitmap FilteringImage = BrightIdeasSoftware.Properties.Resources.Filtering;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether null should be considered as a valid data value.
|
||||
/// If this is true (the default), then a cluster will null as a key will be allow.
|
||||
/// If this is false, object that return a cluster key of null will ignored.
|
||||
/// </summary>
|
||||
public bool TreatNullAsDataValue {
|
||||
get { return treatNullAsDataValue; }
|
||||
set { treatNullAsDataValue = value; }
|
||||
}
|
||||
private bool treatNullAsDataValue = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of objects that the clustering strategy
|
||||
/// will consider. This should be large enough to collect all unique clusters,
|
||||
/// but small enough to finish in a reasonable time.
|
||||
/// </summary>
|
||||
/// <remarks>The default value is 10,000. This should be perfectly
|
||||
/// acceptable for almost all lists.</remarks>
|
||||
public int MaxObjectsToConsider {
|
||||
get { return maxObjectsToConsider; }
|
||||
set { maxObjectsToConsider = value; }
|
||||
}
|
||||
private int maxObjectsToConsider = 10000;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Create a Filter menu on the given tool tip for the given column in the given ObjectListView.
|
||||
/// </summary>
|
||||
/// <remarks>This is the main entry point into this class.</remarks>
|
||||
/// <param name="strip"></param>
|
||||
/// <param name="listView"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns>The strip that should be shown to the user</returns>
|
||||
virtual public ToolStripDropDown MakeFilterMenu(ToolStripDropDown strip, ObjectListView listView, OLVColumn column) {
|
||||
if (strip == null) throw new ArgumentNullException("strip");
|
||||
if (listView == null) throw new ArgumentNullException("listView");
|
||||
if (column == null) throw new ArgumentNullException("column");
|
||||
|
||||
if (!column.UseFiltering || column.ClusteringStrategy == null || listView.Objects == null)
|
||||
return strip;
|
||||
|
||||
List<ICluster> clusters = this.Cluster(column.ClusteringStrategy, listView, column);
|
||||
if (clusters.Count > 0) {
|
||||
this.SortClusters(column.ClusteringStrategy, clusters);
|
||||
strip.Items.Add(this.CreateFilteringMenuItem(column, clusters));
|
||||
}
|
||||
|
||||
return strip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a collection of clusters that should be presented to the user
|
||||
/// </summary>
|
||||
/// <param name="strategy"></param>
|
||||
/// <param name="listView"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
virtual protected List<ICluster> Cluster(IClusteringStrategy strategy, ObjectListView listView, OLVColumn column) {
|
||||
// Build a map that correlates cluster key to clusters
|
||||
NullableDictionary<object, ICluster> map = new NullableDictionary<object, ICluster>();
|
||||
int count = 0;
|
||||
foreach (object model in listView.ObjectsForClustering) {
|
||||
this.ClusterOneModel(strategy, map, model);
|
||||
|
||||
if (count++ > this.MaxObjectsToConsider)
|
||||
break;
|
||||
}
|
||||
|
||||
// Now that we know exactly how many items are in each cluster, create a label for it
|
||||
foreach (ICluster cluster in map.Values)
|
||||
cluster.DisplayLabel = strategy.GetClusterDisplayLabel(cluster);
|
||||
|
||||
return new List<ICluster>(map.Values);
|
||||
}
|
||||
|
||||
private void ClusterOneModel(IClusteringStrategy strategy, NullableDictionary<object, ICluster> map, object model) {
|
||||
object clusterKey = strategy.GetClusterKey(model);
|
||||
|
||||
// If the returned value is an IEnumerable, that means the given model can belong to more than one cluster
|
||||
IEnumerable keyEnumerable = clusterKey as IEnumerable;
|
||||
if (clusterKey is string || keyEnumerable == null)
|
||||
keyEnumerable = new object[] {clusterKey};
|
||||
|
||||
// Deal with nulls and DBNulls
|
||||
ArrayList nullCorrected = new ArrayList();
|
||||
foreach (object key in keyEnumerable) {
|
||||
if (key == null || key == System.DBNull.Value) {
|
||||
if (this.TreatNullAsDataValue)
|
||||
nullCorrected.Add(null);
|
||||
} else nullCorrected.Add(key);
|
||||
}
|
||||
|
||||
// Group by key
|
||||
foreach (object key in nullCorrected) {
|
||||
if (map.ContainsKey(key))
|
||||
map[key].Count += 1;
|
||||
else
|
||||
map[key] = strategy.CreateCluster(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order the given list of clusters in the manner in which they should be presented to the user.
|
||||
/// </summary>
|
||||
/// <param name="strategy"></param>
|
||||
/// <param name="clusters"></param>
|
||||
virtual protected void SortClusters(IClusteringStrategy strategy, List<ICluster> clusters) {
|
||||
clusters.Sort();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do the work of making a menu that shows the clusters to the users
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="clusters"></param>
|
||||
/// <returns></returns>
|
||||
virtual protected ToolStripMenuItem CreateFilteringMenuItem(OLVColumn column, List<ICluster> clusters) {
|
||||
ToolStripCheckedListBox checkedList = new ToolStripCheckedListBox();
|
||||
checkedList.Tag = column;
|
||||
foreach (ICluster cluster in clusters)
|
||||
checkedList.AddItem(cluster, column.ValuesChosenForFiltering.Contains(cluster.ClusterKey));
|
||||
if (!String.IsNullOrEmpty(SELECT_ALL_LABEL)) {
|
||||
int checkedCount = checkedList.CheckedItems.Count;
|
||||
if (checkedCount == 0)
|
||||
checkedList.AddItem(SELECT_ALL_LABEL, CheckState.Unchecked);
|
||||
else
|
||||
checkedList.AddItem(SELECT_ALL_LABEL, checkedCount == clusters.Count ? CheckState.Checked : CheckState.Indeterminate);
|
||||
}
|
||||
checkedList.ItemCheck += new ItemCheckEventHandler(HandleItemCheckedWrapped);
|
||||
|
||||
ToolStripMenuItem clearAll = new ToolStripMenuItem(CLEAR_ALL_FILTERS_LABEL, ClearFilteringImage, delegate(object sender, EventArgs args) {
|
||||
this.ClearAllFilters(column);
|
||||
});
|
||||
ToolStripMenuItem apply = new ToolStripMenuItem(APPLY_LABEL, FilteringImage, delegate(object sender, EventArgs args) {
|
||||
this.EnactFilter(checkedList, column);
|
||||
});
|
||||
ToolStripMenuItem subMenu = new ToolStripMenuItem(FILTERING_LABEL, null, new ToolStripItem[] {
|
||||
clearAll, new ToolStripSeparator(), checkedList, apply });
|
||||
return subMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrap a protected section around the real HandleItemChecked method, so that if
|
||||
/// that method tries to change a "checkedness" of an item, we don't get a recursive
|
||||
/// stack error. Effectively, this ensure that HandleItemChecked is only called
|
||||
/// in response to a user action.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void HandleItemCheckedWrapped(object sender, ItemCheckEventArgs e) {
|
||||
if (alreadyInHandleItemChecked)
|
||||
return;
|
||||
|
||||
try {
|
||||
alreadyInHandleItemChecked = true;
|
||||
this.HandleItemChecked(sender, e);
|
||||
}
|
||||
finally {
|
||||
alreadyInHandleItemChecked = false;
|
||||
}
|
||||
}
|
||||
bool alreadyInHandleItemChecked = false;
|
||||
|
||||
/// <summary>
|
||||
/// Handle a user-generated ItemCheck event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
virtual protected void HandleItemChecked(object sender, ItemCheckEventArgs e) {
|
||||
|
||||
ToolStripCheckedListBox checkedList = sender as ToolStripCheckedListBox;
|
||||
if (checkedList == null) return;
|
||||
OLVColumn column = checkedList.Tag as OLVColumn;
|
||||
if (column == null) return;
|
||||
ObjectListView listView = column.ListView as ObjectListView;
|
||||
if (listView == null) return;
|
||||
|
||||
// Deal with the "Select All" item if there is one
|
||||
int selectAllIndex = checkedList.Items.IndexOf(SELECT_ALL_LABEL);
|
||||
if (selectAllIndex >= 0)
|
||||
HandleSelectAllItem(e, checkedList, selectAllIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle any checking/unchecking of the Select All option, and keep
|
||||
/// its checkedness in sync with everything else that is checked.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
/// <param name="checkedList"></param>
|
||||
/// <param name="selectAllIndex"></param>
|
||||
virtual protected void HandleSelectAllItem(ItemCheckEventArgs e, ToolStripCheckedListBox checkedList, int selectAllIndex) {
|
||||
// Did they check/uncheck the "Select All"?
|
||||
if (e.Index == selectAllIndex) {
|
||||
if (e.NewValue == CheckState.Checked)
|
||||
checkedList.CheckAll();
|
||||
if (e.NewValue == CheckState.Unchecked)
|
||||
checkedList.UncheckAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// OK. The user didn't check/uncheck SelectAll. Now we have to update it's
|
||||
// checkedness to reflect the state of everything else
|
||||
// If all clusters are checked, we check the Select All.
|
||||
// If no clusters are checked, the uncheck the Select All.
|
||||
// For everything else, Select All is set to indeterminate.
|
||||
|
||||
// How many items are currenty checked?
|
||||
int count = checkedList.CheckedItems.Count;
|
||||
|
||||
// First complication.
|
||||
// The value of the Select All itself doesn't count
|
||||
if (checkedList.GetItemCheckState(selectAllIndex) != CheckState.Unchecked)
|
||||
count -= 1;
|
||||
|
||||
// Another complication.
|
||||
// CheckedItems does not yet know about the item the user has just
|
||||
// clicked, so we have to adjust the count of checked items to what
|
||||
// it is going to be
|
||||
if (e.NewValue != e.CurrentValue) {
|
||||
if (e.NewValue == CheckState.Checked)
|
||||
count += 1;
|
||||
else
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
// Update the state of the Select All item
|
||||
if (count == 0)
|
||||
checkedList.SetItemState(selectAllIndex, CheckState.Unchecked);
|
||||
else if (count == checkedList.Items.Count - 1)
|
||||
checkedList.SetItemState(selectAllIndex, CheckState.Checked);
|
||||
else
|
||||
checkedList.SetItemState(selectAllIndex, CheckState.Indeterminate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all the filters that are applied to the given column
|
||||
/// </summary>
|
||||
/// <param name="column">The column from which filters are to be removed</param>
|
||||
virtual protected void ClearAllFilters(OLVColumn column) {
|
||||
|
||||
ObjectListView olv = column.ListView as ObjectListView;
|
||||
if (olv == null || olv.IsDisposed)
|
||||
return;
|
||||
|
||||
olv.ResetColumnFiltering();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the selected values from the given list as a filter on the given column
|
||||
/// </summary>
|
||||
/// <param name="checkedList">A list in which the checked items should be used as filters</param>
|
||||
/// <param name="column">The column for which a filter should be generated</param>
|
||||
virtual protected void EnactFilter(ToolStripCheckedListBox checkedList, OLVColumn column) {
|
||||
|
||||
ObjectListView olv = column.ListView as ObjectListView;
|
||||
if (olv == null || olv.IsDisposed)
|
||||
return;
|
||||
|
||||
// Collect all the checked values
|
||||
ArrayList chosenValues = new ArrayList();
|
||||
foreach (object x in checkedList.CheckedItems) {
|
||||
ICluster cluster = x as ICluster;
|
||||
if (cluster != null) {
|
||||
chosenValues.Add(cluster.ClusterKey);
|
||||
}
|
||||
}
|
||||
column.ValuesChosenForFiltering = chosenValues;
|
||||
|
||||
olv.UpdateColumnFiltering();
|
||||
}
|
||||
}
|
||||
}
|
||||
481
ObjectListView/Filtering/Filters.cs
Normal file
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
* Filters - Filtering on ObjectListViews
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 03/03/2010 17:00
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-01 JPP Added CompositeAllFilter, CompositeAnyFilter and OneOfFilter
|
||||
* v2.4.1
|
||||
* 2010-06-23 JPP Extended TextMatchFilter to handle regular expressions and string prefix matching.
|
||||
* v2.4
|
||||
* 2010-03-03 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
*
|
||||
* Copyright (C) 2010-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for model-by-model filtering
|
||||
/// </summary>
|
||||
public interface IModelFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Should the given model be included when this filter is installed
|
||||
/// </summary>
|
||||
/// <param name="modelObject">The model object to consider</param>
|
||||
/// <returns>Returns true if the model will be included by the filter</returns>
|
||||
bool Filter(object modelObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for whole list filtering
|
||||
/// </summary>
|
||||
public interface IListFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a subset of the given list of model objects as the new
|
||||
/// contents of the ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">The collection of model objects that the list will possibly display</param>
|
||||
/// <returns>The filtered collection that holds the model objects that will be displayed.</returns>
|
||||
IEnumerable Filter(IEnumerable modelObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for model-by-model filters
|
||||
/// </summary>
|
||||
public class AbstractModelFilter : IModelFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Should the given model be included when this filter is installed
|
||||
/// </summary>
|
||||
/// <param name="modelObject">The model object to consider</param>
|
||||
/// <returns>Returns true if the model will be included by the filter</returns>
|
||||
virtual public bool Filter(object modelObject) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This filter calls a given Predicate to decide if a model object should be included
|
||||
/// </summary>
|
||||
public class ModelFilter : IModelFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a filter based on the given predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The function that will filter objects</param>
|
||||
public ModelFilter(Predicate<object> predicate) {
|
||||
this.Predicate = predicate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the predicate used to filter model objects
|
||||
/// </summary>
|
||||
protected Predicate<object> Predicate {
|
||||
get { return predicate; }
|
||||
set { predicate = value; }
|
||||
}
|
||||
private Predicate<object> predicate;
|
||||
|
||||
/// <summary>
|
||||
/// Should the given model object be included?
|
||||
/// </summary>
|
||||
/// <param name="modelObject"></param>
|
||||
/// <returns></returns>
|
||||
virtual public bool Filter(object modelObject) {
|
||||
return this.Predicate == null ? true : this.Predicate(modelObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CompositeFilter joins several other filters together.
|
||||
/// If there are no filters, all model objects are included
|
||||
/// </summary>
|
||||
abstract public class CompositeFilter : IModelFilter {
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty filter
|
||||
/// </summary>
|
||||
public CompositeFilter() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a composite filter from the given list of filters
|
||||
/// </summary>
|
||||
/// <param name="filters">A list of filters</param>
|
||||
public CompositeFilter(IEnumerable<IModelFilter> filters) {
|
||||
foreach (IModelFilter filter in filters) {
|
||||
if (filter != null)
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filters used by this composite
|
||||
/// </summary>
|
||||
public IList<IModelFilter> Filters {
|
||||
get { return filters; }
|
||||
set { filters = value; }
|
||||
}
|
||||
private IList<IModelFilter> filters = new List<IModelFilter>();
|
||||
|
||||
/// <summary>
|
||||
/// Get the sub filters that are text match filters
|
||||
/// </summary>
|
||||
public IEnumerable<TextMatchFilter> TextFilters {
|
||||
get {
|
||||
foreach (IModelFilter filter in this.Filters) {
|
||||
TextMatchFilter textFilter = filter as TextMatchFilter;
|
||||
if (textFilter != null)
|
||||
yield return textFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decide whether or not the given model should be included by the filter
|
||||
/// </summary>
|
||||
/// <param name="modelObject"></param>
|
||||
/// <returns>True if the object is included by the filter</returns>
|
||||
virtual public bool Filter(object modelObject) {
|
||||
if (this.Filters == null || this.Filters.Count == 0)
|
||||
return true;
|
||||
|
||||
return this.FilterObject(modelObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decide whether or not the given model should be included by the filter
|
||||
/// </summary>
|
||||
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
|
||||
/// <param name="modelObject">The model object under consideration</param>
|
||||
/// <returns>True if the object is included by the filter</returns>
|
||||
abstract public bool FilterObject(object modelObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CompositeAllFilter joins several other filters together.
|
||||
/// A model object must satisfy all filters to be included.
|
||||
/// If there are no filters, all model objects are included
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Create a filter
|
||||
/// </remarks>
|
||||
/// <param name="filters"></param>
|
||||
public class CompositeAllFilter(List<IModelFilter> filters) : CompositeFilter(filters) {
|
||||
|
||||
/// <summary>
|
||||
/// Decide whether or not the given model should be included by the filter
|
||||
/// </summary>
|
||||
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
|
||||
/// <param name="modelObject">The model object under consideration</param>
|
||||
/// <returns>True if the object is included by the filter</returns>
|
||||
override public bool FilterObject(object modelObject) {
|
||||
foreach (IModelFilter filter in this.Filters)
|
||||
if (!filter.Filter(modelObject))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CompositeAllFilter joins several other filters together.
|
||||
/// A model object must only satisfy one of the filters to be included.
|
||||
/// If there are no filters, all model objects are included
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Create a filter from the given filters
|
||||
/// </remarks>
|
||||
/// <param name="filters"></param>
|
||||
public class CompositeAnyFilter(List<IModelFilter> filters) : CompositeFilter(filters) {
|
||||
|
||||
/// <summary>
|
||||
/// Decide whether or not the given model should be included by the filter
|
||||
/// </summary>
|
||||
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
|
||||
/// <param name="modelObject">The model object under consideration</param>
|
||||
/// <returns>True if the object is included by the filter</returns>
|
||||
override public bool FilterObject(object modelObject) {
|
||||
foreach (IModelFilter filter in this.Filters)
|
||||
if (filter.Filter(modelObject))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class extract a value from the model object
|
||||
/// and compare that value to a list of fixed values. The model
|
||||
/// object is included if the extracted value is in the list
|
||||
/// </summary>
|
||||
/// <remarks>If there is no delegate installed or there are
|
||||
/// no values to match, no model objects will be matched</remarks>
|
||||
public class OneOfFilter : IModelFilter {
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter that will use the given delegate to extract values
|
||||
/// </summary>
|
||||
/// <param name="valueGetter"></param>
|
||||
public OneOfFilter(AspectGetterDelegate valueGetter) :
|
||||
this(valueGetter, new ArrayList()) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter that will extract values using the given delegate
|
||||
/// and compare them to the values in the given list.
|
||||
/// </summary>
|
||||
/// <param name="valueGetter"></param>
|
||||
/// <param name="possibleValues"></param>
|
||||
public OneOfFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) {
|
||||
this.ValueGetter = valueGetter;
|
||||
this.PossibleValues = new ArrayList(possibleValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate that will be used to extract values
|
||||
/// from model objects
|
||||
/// </summary>
|
||||
virtual public AspectGetterDelegate ValueGetter {
|
||||
get { return valueGetter; }
|
||||
set { valueGetter = value; }
|
||||
}
|
||||
private AspectGetterDelegate valueGetter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of values that the value extracted from
|
||||
/// the model object must match in order to be included.
|
||||
/// </summary>
|
||||
virtual public IList PossibleValues {
|
||||
get { return possibleValues; }
|
||||
set { possibleValues = value; }
|
||||
}
|
||||
private IList possibleValues;
|
||||
|
||||
/// <summary>
|
||||
/// Should the given model object be included?
|
||||
/// </summary>
|
||||
/// <param name="modelObject"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool Filter(object modelObject) {
|
||||
if (this.ValueGetter == null || this.PossibleValues == null || this.PossibleValues.Count == 0)
|
||||
return false;
|
||||
|
||||
object result = this.ValueGetter(modelObject);
|
||||
IEnumerable enumerable = result as IEnumerable;
|
||||
if (result is string || enumerable == null)
|
||||
return this.DoesValueMatch(result);
|
||||
|
||||
foreach (object x in enumerable) {
|
||||
if (this.DoesValueMatch(x))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decides if the given property is a match for the values in the PossibleValues collection
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool DoesValueMatch(object result) {
|
||||
return this.PossibleValues.Contains(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class match a property of a model objects against
|
||||
/// a list of bit flags. The property should be an xor-ed collection
|
||||
/// of bits flags.
|
||||
/// </summary>
|
||||
/// <remarks>Both the property compared and the list of possible values
|
||||
/// must be convertible to ulongs.</remarks>
|
||||
public class FlagBitSetFilter : OneOfFilter {
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance
|
||||
/// </summary>
|
||||
/// <param name="valueGetter"></param>
|
||||
/// <param name="possibleValues"></param>
|
||||
public FlagBitSetFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) : base(valueGetter, possibleValues) {
|
||||
this.ConvertPossibleValues();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of values that will be matched.
|
||||
/// These must be ulongs (or convertible to ulongs).
|
||||
/// </summary>
|
||||
public override IList PossibleValues {
|
||||
get { return base.PossibleValues; }
|
||||
set {
|
||||
base.PossibleValues = value;
|
||||
this.ConvertPossibleValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void ConvertPossibleValues() {
|
||||
this.possibleValuesAsUlongs = new List<UInt64>();
|
||||
foreach (object x in this.PossibleValues)
|
||||
this.possibleValuesAsUlongs.Add(Convert.ToUInt64(x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decides if the given property is a match for the values in the PossibleValues collection
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool DoesValueMatch(object result) {
|
||||
try {
|
||||
UInt64 value = Convert.ToUInt64(result);
|
||||
foreach (ulong flag in this.possibleValuesAsUlongs) {
|
||||
if ((value & flag) == flag)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (InvalidCastException) {
|
||||
return false;
|
||||
}
|
||||
catch (FormatException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private List<UInt64> possibleValuesAsUlongs = new List<UInt64>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for whole list filters
|
||||
/// </summary>
|
||||
public class AbstractListFilter : IListFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a subset of the given list of model objects as the new
|
||||
/// contents of the ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">The collection of model objects that the list will possibly display</param>
|
||||
/// <returns>The filtered collection that holds the model objects that will be displayed.</returns>
|
||||
virtual public IEnumerable Filter(IEnumerable modelObjects) {
|
||||
return modelObjects;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance of this class implement delegate based whole list filtering
|
||||
/// </summary>
|
||||
public class ListFilter : AbstractListFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate that filters on a whole list
|
||||
/// </summary>
|
||||
/// <param name="rowObjects"></param>
|
||||
/// <returns></returns>
|
||||
public delegate IEnumerable ListFilterDelegate(IEnumerable rowObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Create a ListFilter
|
||||
/// </summary>
|
||||
/// <param name="function"></param>
|
||||
public ListFilter(ListFilterDelegate function) {
|
||||
this.Function = function;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate that will filter the list
|
||||
/// </summary>
|
||||
public ListFilterDelegate Function {
|
||||
get { return function; }
|
||||
set { function = value; }
|
||||
}
|
||||
private ListFilterDelegate function;
|
||||
|
||||
/// <summary>
|
||||
/// Do the actual work of filtering
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
/// <returns></returns>
|
||||
public override IEnumerable Filter(IEnumerable modelObjects) {
|
||||
if (this.Function == null)
|
||||
return modelObjects;
|
||||
|
||||
return this.Function(modelObjects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter the list so only the last N entries are displayed
|
||||
/// </summary>
|
||||
public class TailFilter : AbstractListFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a no-op tail filter
|
||||
/// </summary>
|
||||
public TailFilter() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter that includes on the last N model objects
|
||||
/// </summary>
|
||||
/// <param name="numberOfObjects"></param>
|
||||
public TailFilter(int numberOfObjects) {
|
||||
this.Count = numberOfObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of model objects that will be
|
||||
/// returned from the tail of the list
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return count; }
|
||||
set { count = value; }
|
||||
}
|
||||
private int count;
|
||||
|
||||
/// <summary>
|
||||
/// Return the last N subset of the model objects
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
/// <returns></returns>
|
||||
public override IEnumerable Filter(IEnumerable modelObjects) {
|
||||
if (this.Count <= 0)
|
||||
return modelObjects;
|
||||
|
||||
ArrayList list = ObjectListView.EnumerableToArray(modelObjects, false);
|
||||
|
||||
if (this.Count > list.Count)
|
||||
return list;
|
||||
|
||||
object[] tail = new object[this.Count];
|
||||
list.CopyTo(list.Count - this.Count, tail, 0, this.Count);
|
||||
return new ArrayList(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
ObjectListView/Filtering/FlagClusteringStrategy.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* FlagClusteringStrategy - Implements a clustering strategy for a field which is a single integer
|
||||
* containing an XOR'ed collection of bit flags
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 23-March-2012 8:33 am
|
||||
*
|
||||
* Change log:
|
||||
* 2012-03-23 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2012 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class cluster model objects on the basis of a
|
||||
/// property that holds an xor-ed collection of bit flags.
|
||||
/// </summary>
|
||||
public class FlagClusteringStrategy : ClusteringStrategy
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a clustering strategy that operates on the flags of the given enum
|
||||
/// </summary>
|
||||
/// <param name="enumType"></param>
|
||||
public FlagClusteringStrategy(Type enumType) {
|
||||
if (enumType == null) throw new ArgumentNullException("enumType");
|
||||
if (!enumType.IsEnum) throw new ArgumentException("Type must be enum", "enumType");
|
||||
if (enumType.GetCustomAttributes(typeof(FlagsAttribute), false) == null) throw new ArgumentException("Type must have [Flags] attribute", "enumType");
|
||||
|
||||
List<long> flags = new List<long>();
|
||||
foreach (object x in Enum.GetValues(enumType))
|
||||
flags.Add(Convert.ToInt64(x));
|
||||
|
||||
List<string> flagLabels = new List<string>();
|
||||
foreach (string x in Enum.GetNames(enumType))
|
||||
flagLabels.Add(x);
|
||||
|
||||
this.SetValues(flags.ToArray(), flagLabels.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a clustering strategy around the given collections of flags and their display labels.
|
||||
/// There must be the same number of elements in both collections.
|
||||
/// </summary>
|
||||
/// <param name="values">The list of flags. </param>
|
||||
/// <param name="labels"></param>
|
||||
public FlagClusteringStrategy(long[] values, string[] labels) {
|
||||
this.SetValues(values, labels);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value that will be xor-ed to test for the presence of a particular value.
|
||||
/// </summary>
|
||||
public long[] Values {
|
||||
get { return this.values; }
|
||||
private set { this.values = value; }
|
||||
}
|
||||
private long[] values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the labels that will be used when the corresponding Value is XOR present in the data.
|
||||
/// </summary>
|
||||
public string[] Labels {
|
||||
get { return this.labels; }
|
||||
private set { this.labels = value; }
|
||||
}
|
||||
private string[] labels;
|
||||
|
||||
private void SetValues(long[] flags, string[] flagLabels) {
|
||||
if (flags == null || flags.Length == 0) throw new ArgumentNullException("flags");
|
||||
if (flagLabels == null || flagLabels.Length == 0) throw new ArgumentNullException("flagLabels");
|
||||
if (flags.Length != flagLabels.Length) throw new ArgumentException("values and labels must have the same number of entries", "flags");
|
||||
|
||||
this.Values = flags;
|
||||
this.Labels = flagLabels;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IClusteringStrategy
|
||||
|
||||
/// <summary>
|
||||
/// Get the cluster key by which the given model will be partitioned by this strategy
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public override object GetClusterKey(object model) {
|
||||
List<long> flags = new List<long>();
|
||||
try {
|
||||
long modelValue = Convert.ToInt64(this.Column.GetValue(model));
|
||||
foreach (long x in this.Values) {
|
||||
if ((x & modelValue) == x)
|
||||
flags.Add(x);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
catch (InvalidCastException ex) {
|
||||
System.Diagnostics.Debug.Write(ex);
|
||||
return flags;
|
||||
}
|
||||
catch (FormatException ex) {
|
||||
System.Diagnostics.Debug.Write(ex);
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display label that the given cluster should use
|
||||
/// </summary>
|
||||
/// <param name="cluster"></param>
|
||||
/// <returns></returns>
|
||||
public override string GetClusterDisplayLabel(ICluster cluster) {
|
||||
long clusterKeyAsUlong = Convert.ToInt64(cluster.ClusterKey);
|
||||
for (int i = 0; i < this.Values.Length; i++ ) {
|
||||
if (clusterKeyAsUlong == this.Values[i])
|
||||
return this.ApplyDisplayFormat(cluster, this.Labels[i]);
|
||||
}
|
||||
return this.ApplyDisplayFormat(cluster, clusterKeyAsUlong.ToString(CultureInfo.CurrentUICulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter that will include only model objects that
|
||||
/// match one or more of the given values.
|
||||
/// </summary>
|
||||
/// <param name="valuesChosenForFiltering"></param>
|
||||
/// <returns></returns>
|
||||
public override IModelFilter CreateFilter(IList valuesChosenForFiltering) {
|
||||
return new FlagBitSetFilter(this.GetClusterKey, valuesChosenForFiltering);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
56
ObjectListView/Filtering/ICluster.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* ICluster - A cluster is a group of objects that can be included or excluded as a whole
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 4-March-2011 11:59 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-04 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A cluster is a like collection of objects that can be usefully filtered
|
||||
/// as whole using the filtering UI provided by the ObjectListView.
|
||||
/// </summary>
|
||||
public interface ICluster : IComparable {
|
||||
/// <summary>
|
||||
/// Gets or sets how many items belong to this cluster
|
||||
/// </summary>
|
||||
int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label that will be shown to the user to represent
|
||||
/// this cluster
|
||||
/// </summary>
|
||||
string DisplayLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual data object that all members of this cluster
|
||||
/// have commonly returned.
|
||||
/// </summary>
|
||||
object ClusterKey { get; set; }
|
||||
}
|
||||
}
|
||||
80
ObjectListView/Filtering/IClusteringStrategy.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* IClusterStrategy - Encapsulates the ability to create a list of clusters from an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 4-March-2011 11:59 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2012-05-23 JPP - Added CreateFilter() method to interface to allow the strategy
|
||||
* to control the actual model filter that is created.
|
||||
* v2.5
|
||||
* 2011-03-04 JPP - First version
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware{
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of this interface control the selecting of cluster keys
|
||||
/// and how those clusters will be presented to the user
|
||||
/// </summary>
|
||||
public interface IClusteringStrategy {
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column upon which this strategy will operate
|
||||
/// </summary>
|
||||
OLVColumn Column { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the cluster key by which the given model will be partitioned by this strategy
|
||||
/// </summary>
|
||||
/// <remarks>If the returned value is an IEnumerable, the given model is considered
|
||||
/// to belong to multiple clusters</remarks>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
object GetClusterKey(object model);
|
||||
|
||||
/// <summary>
|
||||
/// Create a cluster to hold the given cluster key
|
||||
/// </summary>
|
||||
/// <param name="clusterKey"></param>
|
||||
/// <returns></returns>
|
||||
ICluster CreateCluster(object clusterKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display label that the given cluster should use
|
||||
/// </summary>
|
||||
/// <param name="cluster"></param>
|
||||
/// <returns></returns>
|
||||
string GetClusterDisplayLabel(ICluster cluster);
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter that will include only model objects that
|
||||
/// match one or more of the given values.
|
||||
/// </summary>
|
||||
/// <param name="valuesChosenForFiltering"></param>
|
||||
/// <returns></returns>
|
||||
IModelFilter CreateFilter(IList valuesChosenForFiltering);
|
||||
}
|
||||
}
|
||||
629
ObjectListView/Filtering/TextMatchFilter.cs
Normal file
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* TextMatchFilter - Text based filtering on ObjectListViews
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31/05/2011 7:45am
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-10-13 JPP Allow filtering to consider additional columns
|
||||
* v2.5.1
|
||||
* 2011-06-22 JPP Handle searching for empty strings
|
||||
* v2.5.0
|
||||
* 2011-05-31 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class include only those rows of the listview
|
||||
/// that match one or more given strings.
|
||||
/// </summary>
|
||||
/// <remarks>This class can match strings by prefix, regex, or simple containment.
|
||||
/// There are factory methods for each of these matching strategies.</remarks>
|
||||
public class TextMatchFilter : AbstractModelFilter {
|
||||
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a text filter that will include rows where any cell matches
|
||||
/// any of the given regex expressions.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="texts"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Any string that is not a valid regex expression will be ignored.</remarks>
|
||||
public static TextMatchFilter Regex(ObjectListView olv, params string[] texts) {
|
||||
TextMatchFilter filter = new TextMatchFilter(olv);
|
||||
filter.RegexStrings = texts;
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a text filter that includes rows where any cell begins with one of the given strings
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="texts"></param>
|
||||
/// <returns></returns>
|
||||
public static TextMatchFilter Prefix(ObjectListView olv, params string[] texts) {
|
||||
TextMatchFilter filter = new TextMatchFilter(olv);
|
||||
filter.PrefixStrings = texts;
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a text filter that includes rows where any cell contains any of the given strings.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="texts"></param>
|
||||
/// <returns></returns>
|
||||
public static TextMatchFilter Contains(ObjectListView olv, params string[] texts) {
|
||||
TextMatchFilter filter = new TextMatchFilter(olv);
|
||||
filter.ContainsStrings = texts;
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextFilter
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
public TextMatchFilter(ObjectListView olv) {
|
||||
this.ListView = olv;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextFilter that finds the given string
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="text"></param>
|
||||
public TextMatchFilter(ObjectListView olv, string text) {
|
||||
this.ListView = olv;
|
||||
this.ContainsStrings = new string[] { text };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextFilter that finds the given string using the given comparison
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="comparison"></param>
|
||||
public TextMatchFilter(ObjectListView olv, string text, StringComparison comparison) {
|
||||
this.ListView = olv;
|
||||
this.ContainsStrings = new string[] { text };
|
||||
this.StringComparison = comparison;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which columns will be used for the comparisons? If this is null, all columns will be used
|
||||
/// </summary>
|
||||
public OLVColumn[] Columns {
|
||||
get { return columns; }
|
||||
set { columns = value; }
|
||||
}
|
||||
private OLVColumn[] columns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional columns which will be used in the comparison. These will be used
|
||||
/// in addition to either the Columns property or to all columns taken from the control.
|
||||
/// </summary>
|
||||
public OLVColumn[] AdditionalColumns {
|
||||
get { return additionalColumns; }
|
||||
set { additionalColumns = value; }
|
||||
}
|
||||
private OLVColumn[] additionalColumns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of strings that will be used for
|
||||
/// contains matching. Setting this replaces all previous texts
|
||||
/// of any kind.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ContainsStrings {
|
||||
get {
|
||||
foreach (TextMatchingStrategy component in this.MatchingStrategies)
|
||||
yield return component.Text;
|
||||
}
|
||||
set {
|
||||
this.MatchingStrategies = new List<TextMatchingStrategy>();
|
||||
if (value != null) {
|
||||
foreach (string text in value)
|
||||
this.MatchingStrategies.Add(new TextContainsMatchingStrategy(this, text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this filter has any search criteria
|
||||
/// </summary>
|
||||
public bool HasComponents {
|
||||
get {
|
||||
return this.MatchingStrategies.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the ObjectListView upon which this filter will work
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You cannot really rebase a filter after it is created, so do not change this value.
|
||||
/// It is included so that it can be set in an object initializer.
|
||||
/// </remarks>
|
||||
public ObjectListView ListView {
|
||||
get { return listView; }
|
||||
set { listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of strings that will be used for
|
||||
/// prefix matching. Setting this replaces all previous texts
|
||||
/// of any kind.
|
||||
/// </summary>
|
||||
public IEnumerable<string> PrefixStrings {
|
||||
get {
|
||||
foreach (TextMatchingStrategy component in this.MatchingStrategies)
|
||||
yield return component.Text;
|
||||
}
|
||||
set {
|
||||
this.MatchingStrategies = new List<TextMatchingStrategy>();
|
||||
if (value != null) {
|
||||
foreach (string text in value)
|
||||
this.MatchingStrategies.Add(new TextBeginsMatchingStrategy(this, text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the options that will be used when compiling the regular expression.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used when doing Regex matching (obviously).
|
||||
/// If this is not set specifically, the appropriate options are chosen to match the
|
||||
/// StringComparison setting (culture invariant, case sensitive).
|
||||
/// </remarks>
|
||||
public RegexOptions RegexOptions {
|
||||
get {
|
||||
if (!regexOptions.HasValue) {
|
||||
switch (this.StringComparison) {
|
||||
case StringComparison.CurrentCulture:
|
||||
regexOptions = RegexOptions.None;
|
||||
break;
|
||||
case StringComparison.CurrentCultureIgnoreCase:
|
||||
regexOptions = RegexOptions.IgnoreCase;
|
||||
break;
|
||||
case StringComparison.Ordinal:
|
||||
case StringComparison.InvariantCulture:
|
||||
regexOptions = RegexOptions.CultureInvariant;
|
||||
break;
|
||||
case StringComparison.OrdinalIgnoreCase:
|
||||
case StringComparison.InvariantCultureIgnoreCase:
|
||||
regexOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
|
||||
break;
|
||||
default:
|
||||
regexOptions = RegexOptions.None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return regexOptions.Value;
|
||||
}
|
||||
set {
|
||||
regexOptions = value;
|
||||
}
|
||||
}
|
||||
private RegexOptions? regexOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of strings that will be used for
|
||||
/// regex pattern matching. Setting this replaces all previous texts
|
||||
/// of any kind.
|
||||
/// </summary>
|
||||
public IEnumerable<string> RegexStrings {
|
||||
get {
|
||||
foreach (TextMatchingStrategy component in this.MatchingStrategies)
|
||||
yield return component.Text;
|
||||
}
|
||||
set {
|
||||
this.MatchingStrategies = new List<TextMatchingStrategy>();
|
||||
if (value != null) {
|
||||
foreach (string text in value)
|
||||
this.MatchingStrategies.Add(new TextRegexMatchingStrategy(this, text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the filter will match text
|
||||
/// </summary>
|
||||
public StringComparison StringComparison {
|
||||
get { return this.stringComparison; }
|
||||
set { this.stringComparison = value; }
|
||||
}
|
||||
private StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Loop over the columns that are being considering by the filter
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual IEnumerable<OLVColumn> IterateColumns() {
|
||||
if (this.Columns == null) {
|
||||
foreach (OLVColumn column in this.ListView.Columns)
|
||||
yield return column;
|
||||
} else {
|
||||
foreach (OLVColumn column in this.Columns)
|
||||
yield return column;
|
||||
}
|
||||
if (this.AdditionalColumns != null) {
|
||||
foreach (OLVColumn column in this.AdditionalColumns)
|
||||
yield return column;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Do the actual work of filtering
|
||||
/// </summary>
|
||||
/// <param name="modelObject"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Filter(object modelObject) {
|
||||
if (this.ListView == null || !this.HasComponents)
|
||||
return true;
|
||||
|
||||
foreach (OLVColumn column in this.IterateColumns()) {
|
||||
if (column.IsVisible && column.Searchable) {
|
||||
string[] cellTexts = column.GetSearchValues(modelObject);
|
||||
if (cellTexts != null && cellTexts.Length > 0) {
|
||||
foreach (TextMatchingStrategy filter in this.MatchingStrategies) {
|
||||
if (String.IsNullOrEmpty(filter.Text))
|
||||
return true;
|
||||
foreach (string cellText in cellTexts) {
|
||||
if (filter.MatchesText(cellText))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all the ways in which this filter matches the given string.
|
||||
/// </summary>
|
||||
/// <remarks>This is used by the renderer to decide which bits of
|
||||
/// the string should be highlighted</remarks>
|
||||
/// <param name="cellText"></param>
|
||||
/// <returns>A list of character ranges indicating the matched substrings</returns>
|
||||
public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
|
||||
List<CharacterRange> ranges = new List<CharacterRange>();
|
||||
|
||||
foreach (TextMatchingStrategy filter in this.MatchingStrategies) {
|
||||
if (!String.IsNullOrEmpty(filter.Text))
|
||||
ranges.AddRange(filter.FindAllMatchedRanges(cellText));
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the given column one of the columns being used by this filter?
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsIncluded(OLVColumn column) {
|
||||
if (this.Columns == null) {
|
||||
return column.ListView == this.ListView;
|
||||
}
|
||||
|
||||
foreach (OLVColumn x in this.Columns) {
|
||||
if (x == column)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation members
|
||||
|
||||
private List<TextMatchingStrategy> MatchingStrategies = new List<TextMatchingStrategy>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Components
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the various types of string matching that TextMatchFilter provides
|
||||
/// </summary>
|
||||
abstract protected class TextMatchingStrategy {
|
||||
|
||||
/// <summary>
|
||||
/// Gets how the filter will match text
|
||||
/// </summary>
|
||||
public StringComparison StringComparison {
|
||||
get { return this.TextFilter.StringComparison; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text filter to which this component belongs
|
||||
/// </summary>
|
||||
public TextMatchFilter TextFilter {
|
||||
get { return textFilter; }
|
||||
set { textFilter = value; }
|
||||
}
|
||||
private TextMatchFilter textFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text that will be matched
|
||||
/// </summary>
|
||||
public string Text {
|
||||
get { return this.text; }
|
||||
set { this.text = value; }
|
||||
}
|
||||
private string text;
|
||||
|
||||
/// <summary>
|
||||
/// Find all the ways in which this filter matches the given string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is used by the renderer to decide which bits of
|
||||
/// the string should be highlighted.
|
||||
/// </para>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>A list of character ranges indicating the matched substrings</returns>
|
||||
abstract public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText);
|
||||
|
||||
/// <summary>
|
||||
/// Does the given text match the filter
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>Return true if the given cellText matches our strategy</returns>
|
||||
abstract public bool MatchesText(string cellText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This component provides text contains matching strategy.
|
||||
/// </summary>
|
||||
protected class TextContainsMatchingStrategy : TextMatchingStrategy {
|
||||
|
||||
/// <summary>
|
||||
/// Create a text contains strategy
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="text"></param>
|
||||
public TextContainsMatchingStrategy(TextMatchFilter filter, string text) {
|
||||
this.TextFilter = filter;
|
||||
this.Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the given text match the filter
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>Return true if the given cellText matches our strategy</returns>
|
||||
override public bool MatchesText(string cellText) {
|
||||
return cellText.IndexOf(this.Text, this.StringComparison) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all the ways in which this filter matches the given string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is used by the renderer to decide which bits of
|
||||
/// the string should be highlighted.
|
||||
/// </para>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>A list of character ranges indicating the matched substrings</returns>
|
||||
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
|
||||
List<CharacterRange> ranges = new List<CharacterRange>();
|
||||
|
||||
int matchIndex = cellText.IndexOf(this.Text, this.StringComparison);
|
||||
while (matchIndex != -1) {
|
||||
ranges.Add(new CharacterRange(matchIndex, this.Text.Length));
|
||||
matchIndex = cellText.IndexOf(this.Text, matchIndex + this.Text.Length, this.StringComparison);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This component provides text begins with matching strategy.
|
||||
/// </summary>
|
||||
protected class TextBeginsMatchingStrategy : TextMatchingStrategy {
|
||||
|
||||
/// <summary>
|
||||
/// Create a text begins strategy
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="text"></param>
|
||||
public TextBeginsMatchingStrategy(TextMatchFilter filter, string text) {
|
||||
this.TextFilter = filter;
|
||||
this.Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the given text match the filter
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>Return true if the given cellText matches our strategy</returns>
|
||||
override public bool MatchesText(string cellText) {
|
||||
return cellText.StartsWith(this.Text, this.StringComparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all the ways in which this filter matches the given string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is used by the renderer to decide which bits of
|
||||
/// the string should be highlighted.
|
||||
/// </para>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>A list of character ranges indicating the matched substrings</returns>
|
||||
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
|
||||
List<CharacterRange> ranges = new List<CharacterRange>();
|
||||
|
||||
if (cellText.StartsWith(this.Text, this.StringComparison))
|
||||
ranges.Add(new CharacterRange(0, this.Text.Length));
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This component provides regex matching strategy.
|
||||
/// </summary>
|
||||
protected class TextRegexMatchingStrategy : TextMatchingStrategy {
|
||||
|
||||
/// <summary>
|
||||
/// Creates a regex strategy
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <param name="text"></param>
|
||||
public TextRegexMatchingStrategy(TextMatchFilter filter, string text) {
|
||||
this.TextFilter = filter;
|
||||
this.Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the options that will be used when compiling the regular expression.
|
||||
/// </summary>
|
||||
public RegexOptions RegexOptions {
|
||||
get {
|
||||
return this.TextFilter.RegexOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a compilex regular expression, based on our current Text and RegexOptions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If Text fails to compile as a regular expression, this will return a Regex object
|
||||
/// that will match all strings.
|
||||
/// </remarks>
|
||||
protected Regex Regex {
|
||||
get {
|
||||
if (this.regex == null) {
|
||||
try {
|
||||
this.regex = new Regex(this.Text, this.RegexOptions);
|
||||
}
|
||||
catch (ArgumentException) {
|
||||
this.regex = TextRegexMatchingStrategy.InvalidRegexMarker;
|
||||
}
|
||||
}
|
||||
return this.regex;
|
||||
}
|
||||
set {
|
||||
this.regex = value;
|
||||
}
|
||||
}
|
||||
private Regex regex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not our current regular expression is a valid regex
|
||||
/// </summary>
|
||||
protected bool IsRegexInvalid {
|
||||
get {
|
||||
return this.Regex == TextRegexMatchingStrategy.InvalidRegexMarker;
|
||||
}
|
||||
}
|
||||
static private Regex InvalidRegexMarker = new Regex(".*");
|
||||
|
||||
/// <summary>
|
||||
/// Does the given text match the filter
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>Return true if the given cellText matches our strategy</returns>
|
||||
public override bool MatchesText(string cellText) {
|
||||
if (this.IsRegexInvalid)
|
||||
return true;
|
||||
return this.Regex.Match(cellText).Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all the ways in which this filter matches the given string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is used by the renderer to decide which bits of
|
||||
/// the string should be highlighted.
|
||||
/// </para>
|
||||
/// <para>this.Text will not be null or empty when this is called.</para>
|
||||
/// </remarks>
|
||||
/// <param name="cellText">The text of the cell we want to search</param>
|
||||
/// <returns>A list of character ranges indicating the matched substrings</returns>
|
||||
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
|
||||
List<CharacterRange> ranges = new List<CharacterRange>();
|
||||
|
||||
if (!this.IsRegexInvalid) {
|
||||
foreach (Match match in this.Regex.Matches(cellText)) {
|
||||
if (match.Length > 0)
|
||||
ranges.Add(new CharacterRange(match.Index, match.Length));
|
||||
}
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1261
ObjectListView/FullClassDiagram.cd
Normal file
335
ObjectListView/Implementation/Attributes.cs
Normal file
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Attributes - Attributes that can be attached to properties of models to allow columns to be
|
||||
* built from them directly
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 15/08/2009 22:01
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-08-16 JPP - Added [OLVChildren] and [OLVIgnore]
|
||||
* - OLV attributes can now only be set on properties
|
||||
* v2.4
|
||||
* 2010-04-14 JPP - Allow Name property to be set
|
||||
*
|
||||
* v2.3
|
||||
* 2009-08-15 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute is used to mark a property of a model
|
||||
/// class that should be noticed by Generator class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All the attributes of this class match their equivilent properties on OLVColumn.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OLVColumnAttribute : Attribute
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
// There are several property where we actually want nullable value (bool?, int?),
|
||||
// but it seems attribute properties can't be nullable types.
|
||||
// So we explicitly track if those properties have been set.
|
||||
|
||||
/// <summary>
|
||||
/// Create a new OLVColumnAttribute
|
||||
/// </summary>
|
||||
public OLVColumnAttribute() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new OLVColumnAttribute with the given title
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the column</param>
|
||||
public OLVColumnAttribute(string title) {
|
||||
this.Title = title;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string AspectToStringFormat {
|
||||
get { return aspectToStringFormat; }
|
||||
set { aspectToStringFormat = value; }
|
||||
}
|
||||
private string aspectToStringFormat;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool CheckBoxes {
|
||||
get { return checkBoxes; }
|
||||
set {
|
||||
checkBoxes = value;
|
||||
this.IsCheckBoxesSet = true;
|
||||
}
|
||||
}
|
||||
private bool checkBoxes;
|
||||
internal bool IsCheckBoxesSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int DisplayIndex {
|
||||
get { return displayIndex; }
|
||||
set { displayIndex = value; }
|
||||
}
|
||||
private int displayIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool FillsFreeSpace {
|
||||
get { return fillsFreeSpace; }
|
||||
set { fillsFreeSpace = value; }
|
||||
}
|
||||
private bool fillsFreeSpace;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int FreeSpaceProportion {
|
||||
get { return freeSpaceProportion; }
|
||||
set {
|
||||
freeSpaceProportion = value;
|
||||
IsFreeSpaceProportionSet = true;
|
||||
}
|
||||
}
|
||||
private int freeSpaceProportion;
|
||||
internal bool IsFreeSpaceProportionSet = false;
|
||||
|
||||
/// <summary>
|
||||
/// An array of IComparables that mark the cutoff points for values when
|
||||
/// grouping on this column.
|
||||
/// </summary>
|
||||
public object[] GroupCutoffs {
|
||||
get { return groupCutoffs; }
|
||||
set { groupCutoffs = value; }
|
||||
}
|
||||
private object[] groupCutoffs;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string[] GroupDescriptions {
|
||||
get { return groupDescriptions; }
|
||||
set { groupDescriptions = value; }
|
||||
}
|
||||
private string[] groupDescriptions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string GroupWithItemCountFormat {
|
||||
get { return groupWithItemCountFormat; }
|
||||
set { groupWithItemCountFormat = value; }
|
||||
}
|
||||
private string groupWithItemCountFormat;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string GroupWithItemCountSingularFormat {
|
||||
get { return groupWithItemCountSingularFormat; }
|
||||
set { groupWithItemCountSingularFormat = value; }
|
||||
}
|
||||
private string groupWithItemCountSingularFormat;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Hyperlink {
|
||||
get { return hyperlink; }
|
||||
set { hyperlink = value; }
|
||||
}
|
||||
private bool hyperlink;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string ImageAspectName {
|
||||
get { return imageAspectName; }
|
||||
set { imageAspectName = value; }
|
||||
}
|
||||
private string imageAspectName;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsEditable {
|
||||
get { return isEditable; }
|
||||
set {
|
||||
isEditable = value;
|
||||
this.IsEditableSet = true;
|
||||
}
|
||||
}
|
||||
private bool isEditable = true;
|
||||
internal bool IsEditableSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsVisible {
|
||||
get { return isVisible; }
|
||||
set { isVisible = value; }
|
||||
}
|
||||
private bool isVisible = true;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsTileViewColumn {
|
||||
get { return isTileViewColumn; }
|
||||
set { isTileViewColumn = value; }
|
||||
}
|
||||
private bool isTileViewColumn;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int MaximumWidth {
|
||||
get { return maximumWidth; }
|
||||
set { maximumWidth = value; }
|
||||
}
|
||||
private int maximumWidth = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int MinimumWidth {
|
||||
get { return minimumWidth; }
|
||||
set { minimumWidth = value; }
|
||||
}
|
||||
private int minimumWidth = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String Name {
|
||||
get { return name; }
|
||||
set { name = value; }
|
||||
}
|
||||
private String name;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HorizontalAlignment TextAlign {
|
||||
get { return this.textAlign; }
|
||||
set {
|
||||
this.textAlign = value;
|
||||
IsTextAlignSet = true;
|
||||
}
|
||||
}
|
||||
private HorizontalAlignment textAlign = HorizontalAlignment.Left;
|
||||
internal bool IsTextAlignSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String Tag {
|
||||
get { return tag; }
|
||||
set { tag = value; }
|
||||
}
|
||||
private String tag;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String Title {
|
||||
get { return title; }
|
||||
set { title = value; }
|
||||
}
|
||||
private String title;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String ToolTipText {
|
||||
get { return toolTipText; }
|
||||
set { toolTipText = value; }
|
||||
}
|
||||
private String toolTipText;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool TriStateCheckBoxes {
|
||||
get { return triStateCheckBoxes; }
|
||||
set {
|
||||
triStateCheckBoxes = value;
|
||||
this.IsTriStateCheckBoxesSet = true;
|
||||
}
|
||||
}
|
||||
private bool triStateCheckBoxes;
|
||||
internal bool IsTriStateCheckBoxesSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool UseInitialLetterForGroup {
|
||||
get { return useInitialLetterForGroup; }
|
||||
set { useInitialLetterForGroup = value; }
|
||||
}
|
||||
private bool useInitialLetterForGroup;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Width {
|
||||
get { return width; }
|
||||
set { width = value; }
|
||||
}
|
||||
private int width = 150;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties marked with [OLVChildren] will be used as the children source in a TreeListView.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OLVChildrenAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties marked with [OLVIgnore] will not have columns generated for them.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OLVIgnoreAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
315
ObjectListView/Implementation/Comparers.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Comparers - Various Comparer classes used within ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 25/11/2008 17:15
|
||||
*
|
||||
* Change log:
|
||||
* v2.8.1
|
||||
* 2014-12-03 JPP - Added StringComparer
|
||||
* v2.3
|
||||
* 2009-08-24 JPP - Added OLVGroupComparer
|
||||
* 2009-06-01 JPP - ModelObjectComparer would crash if secondary sort column was null.
|
||||
* 2008-12-20 JPP - Fixed bug with group comparisons when a group key was null (SF#2445761)
|
||||
* 2008-11-25 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
*
|
||||
* Copyright (C) 2006-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// ColumnComparer is the workhorse for all comparison between two values of a particular column.
|
||||
/// If the column has a specific comparer, use that to compare the values. Otherwise, do
|
||||
/// a case insensitive string compare of the string representations of the values.
|
||||
/// </summary>
|
||||
/// <remarks><para>This class inherits from both IComparer and its generic counterpart
|
||||
/// so that it can be used on untyped and typed collections.</para>
|
||||
/// <para>This is used by normal (non-virtual) ObjectListViews. Virtual lists use
|
||||
/// ModelObjectComparer</para>
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column
|
||||
/// </remarks>
|
||||
/// <param name="col">The column whose values will be compared</param>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public class ColumnComparer(OLVColumn col, SortOrder order) : IComparer, IComparer<OLVListItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the method that will be used to compare two strings.
|
||||
/// The default is to compare on the current culture, case-insensitive
|
||||
/// </summary>
|
||||
public static StringCompareDelegate StringComparer
|
||||
{
|
||||
get { return stringComparer; }
|
||||
set { stringComparer = value; }
|
||||
}
|
||||
private static StringCompareDelegate stringComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column, and by a secondary column if the primary
|
||||
/// column is equal.
|
||||
/// </summary>
|
||||
/// <param name="col">The column whose values will be compared</param>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
/// <param name="col2">The column whose values will be compared for secondary sorting</param>
|
||||
/// <param name="order2">The ordering for secondary column values</param>
|
||||
public ColumnComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2)
|
||||
: this(col, order)
|
||||
{
|
||||
// There is no point in secondary sorting on the same column
|
||||
if (col != col2)
|
||||
this.secondComparer = new ColumnComparer(col2, order2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two rows
|
||||
/// </summary>
|
||||
/// <param name="x">row1</param>
|
||||
/// <param name="y">row2</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
return this.Compare((OLVListItem)x, (OLVListItem)y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two rows
|
||||
/// </summary>
|
||||
/// <param name="x">row1</param>
|
||||
/// <param name="y">row2</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int Compare(OLVListItem x, OLVListItem y)
|
||||
{
|
||||
if (this.sortOrder == SortOrder.None)
|
||||
return 0;
|
||||
|
||||
int result = 0;
|
||||
object x1 = this.column.GetValue(x.RowObject);
|
||||
object y1 = this.column.GetValue(y.RowObject);
|
||||
|
||||
// Handle nulls. Null values come last
|
||||
bool xIsNull = (x1 == null || x1 == System.DBNull.Value);
|
||||
bool yIsNull = (y1 == null || y1 == System.DBNull.Value);
|
||||
if (xIsNull || yIsNull) {
|
||||
if (xIsNull && yIsNull)
|
||||
result = 0;
|
||||
else
|
||||
result = (xIsNull ? -1 : 1);
|
||||
} else {
|
||||
result = this.CompareValues(x1, y1);
|
||||
}
|
||||
|
||||
if (this.sortOrder == SortOrder.Descending)
|
||||
result = 0 - result;
|
||||
|
||||
// If the result was equality, use the secondary comparer to resolve it
|
||||
if (result == 0 && this.secondComparer != null)
|
||||
result = this.secondComparer.Compare(x, y);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the actual values to be used for sorting
|
||||
/// </summary>
|
||||
/// <param name="x">The aspect extracted from the first row</param>
|
||||
/// <param name="y">The aspect extracted from the second row</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int CompareValues(object x, object y)
|
||||
{
|
||||
// Force case insensitive compares on strings
|
||||
String xAsString = x as String;
|
||||
if (xAsString != null)
|
||||
return CompareStrings(xAsString, y as String);
|
||||
|
||||
IComparable comparable = x as IComparable;
|
||||
return comparable != null ? comparable.CompareTo(y) : 0;
|
||||
}
|
||||
|
||||
private static int CompareStrings(string x, string y)
|
||||
{
|
||||
if (StringComparer == null)
|
||||
return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase);
|
||||
else
|
||||
return StringComparer(x, y);
|
||||
}
|
||||
|
||||
private OLVColumn column = col;
|
||||
private SortOrder sortOrder = order;
|
||||
private ColumnComparer secondComparer;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This comparer sort list view groups. OLVGroups have a "SortValue" property,
|
||||
/// which is used if present. Otherwise, the titles of the groups will be compared.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Create a group comparer
|
||||
/// </remarks>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public class OLVGroupComparer(SortOrder order) : IComparer<OLVGroup>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two groups. OLVGroups have a "SortValue" property,
|
||||
/// which is used if present. Otherwise, the titles of the groups will be compared.
|
||||
/// </summary>
|
||||
/// <param name="x">group1</param>
|
||||
/// <param name="y">group2</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int Compare(OLVGroup x, OLVGroup y) {
|
||||
// If we can compare the sort values, do that.
|
||||
// Otherwise do a case insensitive compare on the group header.
|
||||
int result;
|
||||
if (x.SortValue != null && y.SortValue != null)
|
||||
result = x.SortValue.CompareTo(y.SortValue);
|
||||
else
|
||||
result = String.Compare(x.Header, y.Header, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
if (this.sortOrder == SortOrder.Descending)
|
||||
result = 0 - result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SortOrder sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This comparer can be used to sort a collection of model objects by a given column
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This is used by virtual ObjectListViews. Non-virtual lists use
|
||||
/// ColumnComparer</para>
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Create a model object comparer
|
||||
/// </remarks>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="order"></param>
|
||||
public class ModelObjectComparer(OLVColumn col, SortOrder order) : IComparer, IComparer<object>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the method that will be used to compare two strings.
|
||||
/// The default is to compare on the current culture, case-insensitive
|
||||
/// </summary>
|
||||
public static StringCompareDelegate StringComparer
|
||||
{
|
||||
get { return stringComparer; }
|
||||
set { stringComparer = value; }
|
||||
}
|
||||
private static StringCompareDelegate stringComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Create a model object comparer with a secondary sorting column
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="order"></param>
|
||||
/// <param name="col2"></param>
|
||||
/// <param name="order2"></param>
|
||||
public ModelObjectComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2)
|
||||
: this(col, order)
|
||||
{
|
||||
// There is no point in secondary sorting on the same column
|
||||
if (col != col2 && col2 != null && order2 != SortOrder.None)
|
||||
this.secondComparer = new ModelObjectComparer(col2, order2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two model objects
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
int result = 0;
|
||||
object x1 = this.column.GetValue(x);
|
||||
object y1 = this.column.GetValue(y);
|
||||
|
||||
if (this.sortOrder == SortOrder.None)
|
||||
return 0;
|
||||
|
||||
// Handle nulls. Null values come last
|
||||
bool xIsNull = (x1 == null || x1 == System.DBNull.Value);
|
||||
bool yIsNull = (y1 == null || y1 == System.DBNull.Value);
|
||||
if (xIsNull || yIsNull) {
|
||||
if (xIsNull && yIsNull)
|
||||
result = 0;
|
||||
else
|
||||
result = (xIsNull ? -1 : 1);
|
||||
} else {
|
||||
result = this.CompareValues(x1, y1);
|
||||
}
|
||||
|
||||
if (this.sortOrder == SortOrder.Descending)
|
||||
result = 0 - result;
|
||||
|
||||
// If the result was equality, use the secondary comparer to resolve it
|
||||
if (result == 0 && this.secondComparer != null)
|
||||
result = this.secondComparer.Compare(x, y);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the actual values
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public int CompareValues(object x, object y)
|
||||
{
|
||||
// Force case insensitive compares on strings
|
||||
String xStr = x as String;
|
||||
if (xStr != null)
|
||||
return CompareStrings(xStr, y as String);
|
||||
|
||||
IComparable comparable = x as IComparable;
|
||||
return comparable != null ? comparable.CompareTo(y) : 0;
|
||||
}
|
||||
|
||||
private static int CompareStrings(string x, string y)
|
||||
{
|
||||
if (StringComparer == null)
|
||||
return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase);
|
||||
else
|
||||
return StringComparer(x, y);
|
||||
}
|
||||
|
||||
private OLVColumn column = col;
|
||||
private SortOrder sortOrder = order;
|
||||
private ModelObjectComparer secondComparer;
|
||||
|
||||
#region IComparer<object> Members
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
630
ObjectListView/Implementation/DataSourceAdapter.cs
Normal file
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
* DataSourceAdapter - A helper class that translates DataSource events for an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 20/09/2010 7:42 AM
|
||||
*
|
||||
* Change log:
|
||||
* v2.9
|
||||
* 2015-10-31 JPP - Put back sanity check on upper limit of source items
|
||||
* 2015-02-02 JPP - Made CreateColumnsFromSource() only rebuild columns when new ones were added
|
||||
* v2.8.1
|
||||
* 2014-11-23 JPP - Honour initial CurrencyManager.Position when setting DataSource.
|
||||
* 2014-10-27 JPP - Fix issue where SelectedObject was not sync'ed with CurrencyManager.Position (SF #129)
|
||||
* v2.6
|
||||
* 2012-08-16 JPP - Unify common column creation functionality with Generator when possible
|
||||
*
|
||||
* 2010-09-20 JPP - Initial version
|
||||
*
|
||||
* Copyright (C) 2010-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that translates DataSource events for an ObjectListView
|
||||
/// </summary>
|
||||
public class DataSourceAdapter : IDisposable
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Make a DataSourceAdapter
|
||||
/// </summary>
|
||||
public DataSourceAdapter(ObjectListView olv) {
|
||||
if (olv == null) throw new ArgumentNullException("olv");
|
||||
|
||||
this.ListView = olv;
|
||||
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
|
||||
this.BindListView(this.ListView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalize this object
|
||||
/// </summary>
|
||||
~DataSourceAdapter() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all the resources used by this instance
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all the resources used by this instance
|
||||
/// </summary>
|
||||
public virtual void Dispose(bool fromUser) {
|
||||
this.UnbindListView(this.ListView);
|
||||
this.UnbindDataSource();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not columns will be automatically generated to show the
|
||||
/// columns when the DataSource is set.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
|
||||
public bool AutoGenerateColumns {
|
||||
get { return this.autoGenerateColumns; }
|
||||
set { this.autoGenerateColumns = value; }
|
||||
}
|
||||
private bool autoGenerateColumns = true;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the DataSource that will be displayed in this list view.
|
||||
/// </summary>
|
||||
public virtual Object DataSource {
|
||||
get { return dataSource; }
|
||||
set {
|
||||
dataSource = value;
|
||||
this.RebindDataSource(true);
|
||||
}
|
||||
}
|
||||
private Object dataSource;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
|
||||
/// </summary>
|
||||
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
|
||||
public virtual string DataMember {
|
||||
get { return dataMember; }
|
||||
set {
|
||||
if (dataMember != value) {
|
||||
dataMember = value;
|
||||
RebindDataSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string dataMember = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView upon which this adaptor will operate
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return listView; }
|
||||
internal set { listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currency manager which is handling our binding context
|
||||
/// </summary>
|
||||
protected CurrencyManager CurrencyManager {
|
||||
get { return currencyManager; }
|
||||
set { currencyManager = value; }
|
||||
}
|
||||
private CurrencyManager currencyManager;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Binding and unbinding
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
protected virtual void BindListView(ObjectListView olv) {
|
||||
if (olv == null)
|
||||
return;
|
||||
|
||||
olv.Freezing += new EventHandler<FreezeEventArgs>(HandleListViewFreezing);
|
||||
olv.SelectionChanged += new EventHandler(HandleListViewSelectionChanged);
|
||||
olv.BindingContextChanged += new EventHandler(HandleListViewBindingContextChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
protected virtual void UnbindListView(ObjectListView olv) {
|
||||
if (olv == null)
|
||||
return;
|
||||
|
||||
olv.Freezing -= new EventHandler<FreezeEventArgs>(HandleListViewFreezing);
|
||||
olv.SelectionChanged -= new EventHandler(HandleListViewSelectionChanged);
|
||||
olv.BindingContextChanged -= new EventHandler(HandleListViewBindingContextChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected virtual void BindDataSource() {
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
this.CurrencyManager.MetaDataChanged += new EventHandler(HandleCurrencyManagerMetaDataChanged);
|
||||
this.CurrencyManager.PositionChanged += new EventHandler(HandleCurrencyManagerPositionChanged);
|
||||
this.CurrencyManager.ListChanged += new ListChangedEventHandler(CurrencyManagerListChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected virtual void UnbindDataSource() {
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
this.CurrencyManager.MetaDataChanged -= new EventHandler(HandleCurrencyManagerMetaDataChanged);
|
||||
this.CurrencyManager.PositionChanged -= new EventHandler(HandleCurrencyManagerPositionChanged);
|
||||
this.CurrencyManager.ListChanged -= new ListChangedEventHandler(CurrencyManagerListChanged);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Our data source has changed. Figure out how to handle the new source
|
||||
/// </summary>
|
||||
protected virtual void RebindDataSource() {
|
||||
RebindDataSource(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Our data source has changed. Figure out how to handle the new source
|
||||
/// </summary>
|
||||
protected virtual void RebindDataSource(bool forceDataInitialization) {
|
||||
|
||||
CurrencyManager tempCurrencyManager = null;
|
||||
if (this.ListView != null && this.ListView.BindingContext != null && this.DataSource != null) {
|
||||
tempCurrencyManager = this.ListView.BindingContext[this.DataSource, this.DataMember] as CurrencyManager;
|
||||
}
|
||||
|
||||
// Has our currency manager changed?
|
||||
if (this.CurrencyManager != tempCurrencyManager) {
|
||||
this.UnbindDataSource();
|
||||
this.CurrencyManager = tempCurrencyManager;
|
||||
this.BindDataSource();
|
||||
|
||||
// Our currency manager has changed so we have to initialize a new data source
|
||||
forceDataInitialization = true;
|
||||
}
|
||||
|
||||
if (forceDataInitialization)
|
||||
InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data source for this control has changed. Reconfigure the control for the new source
|
||||
/// </summary>
|
||||
protected virtual void InitializeDataSource() {
|
||||
if (this.ListView.Frozen || this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
this.CreateColumnsFromSource();
|
||||
this.CreateMissingAspectGettersAndPutters();
|
||||
this.SetListContents();
|
||||
this.ListView.AutoSizeColumns();
|
||||
|
||||
// Fake a position change event so that the control matches any initial Position
|
||||
this.HandleCurrencyManagerPositionChanged(null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take the contents of the currently bound list and put them into the control
|
||||
/// </summary>
|
||||
protected virtual void SetListContents() {
|
||||
this.ListView.Objects = this.CurrencyManager.List;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create columns for the listview based on what properties are available in the data source
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method will create columns if there is not already a column displaying that property.</para>
|
||||
/// </remarks>
|
||||
protected virtual void CreateColumnsFromSource() {
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
// Don't generate any columns in design mode. If we do, the user will see them,
|
||||
// but the Designer won't know about them and won't persist them, which is very confusing
|
||||
if (this.ListView.IsDesignMode)
|
||||
return;
|
||||
|
||||
// Don't create columns if we've been told not to
|
||||
if (!this.AutoGenerateColumns)
|
||||
return;
|
||||
|
||||
// Use a Generator to create columns
|
||||
Generator generator = Generator.Instance as Generator ?? new Generator();
|
||||
|
||||
PropertyDescriptorCollection properties = this.CurrencyManager.GetItemProperties();
|
||||
if (properties.Count == 0)
|
||||
return;
|
||||
|
||||
bool wereColumnsAdded = false;
|
||||
foreach (PropertyDescriptor property in properties) {
|
||||
|
||||
if (!this.ShouldCreateColumn(property))
|
||||
continue;
|
||||
|
||||
// Create a column
|
||||
OLVColumn column = generator.MakeColumnFromPropertyDescriptor(property);
|
||||
this.ConfigureColumn(column, property);
|
||||
|
||||
// Add it to our list
|
||||
this.ListView.AllColumns.Add(column);
|
||||
wereColumnsAdded = true;
|
||||
}
|
||||
|
||||
if (wereColumnsAdded)
|
||||
generator.PostCreateColumns(this.ListView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decide if a new column should be added to the control to display
|
||||
/// the given property
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ShouldCreateColumn(PropertyDescriptor property) {
|
||||
|
||||
// Is there a column that already shows this property? If so, we don't show it again
|
||||
if (this.ListView.AllColumns.Exists(delegate(OLVColumn x) { return x.AspectName == property.Name; }))
|
||||
return false;
|
||||
|
||||
// Relationships to other tables turn up as IBindibleLists. Don't make columns to show them.
|
||||
// CHECK: Is this always true? What other things could be here? Constraints? Triggers?
|
||||
if (property.PropertyType == typeof(IBindingList))
|
||||
return false;
|
||||
|
||||
// Ignore anything marked with [OLVIgnore]
|
||||
return property.Attributes[typeof(OLVIgnoreAttribute)] == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the given column to show the given property.
|
||||
/// The title and aspect name of the column are already filled in.
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="property"></param>
|
||||
protected virtual void ConfigureColumn(OLVColumn column, PropertyDescriptor property) {
|
||||
|
||||
column.LastDisplayIndex = this.ListView.AllColumns.Count;
|
||||
|
||||
// If our column is a BLOB, it could be an image, so assign a renderer to draw it.
|
||||
// CONSIDER: Is this a common enough case to warrant this code?
|
||||
if (property.PropertyType == typeof(System.Byte[]))
|
||||
column.Renderer = new ImageRenderer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate aspect getters and putters for any columns that are missing them (and for which we have
|
||||
/// enough information to actually generate a getter)
|
||||
/// </summary>
|
||||
protected virtual void CreateMissingAspectGettersAndPutters() {
|
||||
foreach (OLVColumn x in this.ListView.AllColumns) {
|
||||
OLVColumn column = x; // stack based variable accessible from closures
|
||||
if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName)) {
|
||||
column.AspectGetter = delegate(object row) {
|
||||
// In most cases, rows will be DataRowView objects
|
||||
DataRowView drv = row as DataRowView;
|
||||
if (drv == null)
|
||||
return column.GetAspectByName(row);
|
||||
return (drv.Row.RowState == DataRowState.Detached) ? null : drv[column.AspectName];
|
||||
};
|
||||
}
|
||||
if (column.IsEditable && column.AspectPutter == null && !String.IsNullOrEmpty(column.AspectName)) {
|
||||
column.AspectPutter = delegate(object row, object newValue) {
|
||||
// In most cases, rows will be DataRowView objects
|
||||
DataRowView drv = row as DataRowView;
|
||||
if (drv == null)
|
||||
column.PutAspectByName(row, newValue);
|
||||
else {
|
||||
if (drv.Row.RowState != DataRowState.Detached)
|
||||
drv[column.AspectName] = newValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// CurrencyManager ListChanged event handler.
|
||||
/// Deals with fine-grained changes to list items.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It's actually difficult to deal with these changes in a fine-grained manner.
|
||||
/// If our listview is grouped, then any change may make a new group appear or
|
||||
/// an old group disappear. It is rarely enough to simply update the affected row.
|
||||
/// </remarks>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void CurrencyManagerListChanged(object sender, ListChangedEventArgs e) {
|
||||
Debug.Assert(sender == this.CurrencyManager);
|
||||
|
||||
// Ignore changes make while frozen, since we will do a complete rebuild when we unfreeze
|
||||
if (this.ListView.Frozen)
|
||||
return;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine(e.ListChangedType);
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
switch (e.ListChangedType) {
|
||||
|
||||
case ListChangedType.Reset:
|
||||
this.HandleListChangedReset(e);
|
||||
break;
|
||||
|
||||
case ListChangedType.ItemChanged:
|
||||
this.HandleListChangedItemChanged(e);
|
||||
break;
|
||||
|
||||
case ListChangedType.ItemAdded:
|
||||
this.HandleListChangedItemAdded(e);
|
||||
break;
|
||||
|
||||
// An item has gone away.
|
||||
case ListChangedType.ItemDeleted:
|
||||
this.HandleListChangedItemDeleted(e);
|
||||
break;
|
||||
|
||||
// An item has changed its index.
|
||||
case ListChangedType.ItemMoved:
|
||||
this.HandleListChangedItemMoved(e);
|
||||
break;
|
||||
|
||||
// Something has changed in the metadata.
|
||||
// CHECK: When are these events actually fired?
|
||||
case ListChangedType.PropertyDescriptorAdded:
|
||||
case ListChangedType.PropertyDescriptorChanged:
|
||||
case ListChangedType.PropertyDescriptorDeleted:
|
||||
this.HandleListChangedMetadataChanged(e);
|
||||
break;
|
||||
}
|
||||
sw.Stop();
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("PERF - Processing {0} event on {1} rows took {2}ms", e.ListChangedType, this.ListView.GetItemCount(), sw.ElapsedMilliseconds));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle PropertyDescriptor* events
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedMetadataChanged(ListChangedEventArgs e) {
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle ItemMoved event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedItemMoved(ListChangedEventArgs e) {
|
||||
// When is this actually triggered?
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the ItemDeleted event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedItemDeleted(ListChangedEventArgs e) {
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle an ItemAdded event.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedItemAdded(ListChangedEventArgs e) {
|
||||
// We get this event twice if certain grid controls are used to add a new row to a
|
||||
// datatable: once when the editing of a new row begins, and once again when that
|
||||
// editing commits. (If the user cancels the creation of the new row, we never see
|
||||
// the second creation.) We detect this by seeing if this is a view on a row in a
|
||||
// DataTable, and if it is, testing to see if it's a new row under creation.
|
||||
|
||||
Object newRow = this.CurrencyManager.List[e.NewIndex];
|
||||
DataRowView drv = newRow as DataRowView;
|
||||
if (drv == null || !drv.IsNew) {
|
||||
// Either we're not dealing with a view on a data table, or this is the commit
|
||||
// notification. Either way, this is the final notification, so we want to
|
||||
// handle the new row now!
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the Reset event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedReset(ListChangedEventArgs e) {
|
||||
// The whole list has changed utterly, so reload it.
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle ItemChanged event. This is triggered when a single item
|
||||
/// has changed, so just refresh that one item.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>Even in this simple case, we should probably rebuild the list.
|
||||
/// For example, the change could put the item into its own new group.</remarks>
|
||||
protected virtual void HandleListChangedItemChanged(ListChangedEventArgs e) {
|
||||
// A single item has changed, so just refresh that.
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("HandleListChangedItemChanged: {0}, {1}", e.NewIndex, e.PropertyDescriptor.Name));
|
||||
|
||||
Object changedRow = this.CurrencyManager.List[e.NewIndex];
|
||||
this.ListView.RefreshObject(changedRow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CurrencyManager calls this if the data source looks
|
||||
/// different. We just reload everything.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// CHECK: Do we need this if we are handle ListChanged metadata events?
|
||||
/// </remarks>
|
||||
protected virtual void HandleCurrencyManagerMetaDataChanged(object sender, EventArgs e) {
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the CurrencyManager when the currently selected item
|
||||
/// changes. We update the ListView selection so that we stay in sync
|
||||
/// with any other controls bound to the same source.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleCurrencyManagerPositionChanged(object sender, EventArgs e) {
|
||||
int index = this.CurrencyManager.Position;
|
||||
|
||||
// Make sure the index is sane (-1 pops up from time to time)
|
||||
if (index < 0 || index >= this.ListView.GetItemCount())
|
||||
return;
|
||||
|
||||
// Avoid recursion. If we are currently changing the index, don't
|
||||
// start the process again.
|
||||
if (this.isChangingIndex)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.isChangingIndex = true;
|
||||
this.ChangePosition(index);
|
||||
}
|
||||
finally {
|
||||
this.isChangingIndex = false;
|
||||
}
|
||||
}
|
||||
private bool isChangingIndex = false;
|
||||
|
||||
/// <summary>
|
||||
/// Change the control's position (which is it's currently selected row)
|
||||
/// to the nth row in the dataset
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the row to be selected</param>
|
||||
protected virtual void ChangePosition(int index) {
|
||||
// We can't use the index directly, since our listview may be sorted
|
||||
// Only assign if not null
|
||||
if (this.ListView.SelectedObject != null)
|
||||
{
|
||||
this.ListView.SelectedObject = this.CurrencyManager.List[index];
|
||||
}
|
||||
|
||||
// THINK: Do we always want to bring it into view?
|
||||
if (this.ListView.SelectedIndices.Count > 0)
|
||||
this.ListView.EnsureVisible(this.ListView.SelectedIndices[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ObjectListView event handlers
|
||||
|
||||
/// <summary>
|
||||
/// Handle the selection changing in our ListView.
|
||||
/// We need to tell our currency manager about the new position.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListViewSelectionChanged(object sender, EventArgs e) {
|
||||
// Prevent recursion
|
||||
if (this.isChangingIndex)
|
||||
return;
|
||||
|
||||
// Sanity
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
// If only one item is selected, tell the currency manager which item is selected.
|
||||
// CurrencyManager can't handle multiple selection so there's nothing we can do
|
||||
// if more than one row is selected.
|
||||
if (this.ListView.SelectedIndices.Count != 1)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.isChangingIndex = true;
|
||||
|
||||
// We can't use the selectedIndex directly, since our listview may be sorted and/or filtered
|
||||
// So we have to find the index of the selected object within the original list.
|
||||
this.CurrencyManager.Position = this.CurrencyManager.List.IndexOf(this.ListView.SelectedObject);
|
||||
} finally {
|
||||
this.isChangingIndex = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the frozenness of our ListView changing.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListViewFreezing(object sender, FreezeEventArgs e) {
|
||||
if (!alreadyFreezing && e.FreezeLevel == 0) {
|
||||
try {
|
||||
alreadyFreezing = true;
|
||||
this.RebindDataSource(true);
|
||||
} finally {
|
||||
alreadyFreezing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool alreadyFreezing = false;
|
||||
|
||||
/// <summary>
|
||||
/// Handle a change to the BindingContext of our ListView.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListViewBindingContextChanged(object sender, EventArgs e) {
|
||||
this.RebindDataSource(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
168
ObjectListView/Implementation/Delegates.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Delegates - All delegate definitions used in ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* v2.10
|
||||
* 2015-12-30 JPP - Added CellRendererGetterDelegate
|
||||
* v2.?
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2015 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
#region Delegate declarations
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to extract an aspect from a row object
|
||||
/// </summary>
|
||||
public delegate Object AspectGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to put a changed value back into a model object
|
||||
/// </summary>
|
||||
public delegate void AspectPutterDelegate(Object rowObject, Object newValue);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates can be used to convert an aspect value to a display string,
|
||||
/// instead of using the default ToString()
|
||||
/// </summary>
|
||||
public delegate string AspectToStringConverterDelegate(Object value);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the tooltip for a cell
|
||||
/// </summary>
|
||||
public delegate String CellToolTipGetterDelegate(OLVColumn column, Object modelObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to the state of the checkbox for a row object.
|
||||
/// </summary>
|
||||
/// <remarks><para>
|
||||
/// For reasons known only to someone in Microsoft, we can only set
|
||||
/// a boolean on the ListViewItem to indicate it's "checked-ness", but when
|
||||
/// we receive update events, we have to use a tristate CheckState. So we can
|
||||
/// be told about an indeterminate state, but we can't set it ourselves.
|
||||
/// </para>
|
||||
/// <para>As of version 2.0, we can now return indeterminate state.</para>
|
||||
/// </remarks>
|
||||
public delegate CheckState CheckStateGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the state of the checkbox for a row object.
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool BooleanCheckStateGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to put a changed check state back into a model object
|
||||
/// </summary>
|
||||
public delegate CheckState CheckStatePutterDelegate(Object rowObject, CheckState newValue);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to put a changed check state back into a model object
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool BooleanCheckStatePutterDelegate(Object rowObject, bool newValue);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the renderer for a particular cell
|
||||
/// </summary>
|
||||
public delegate IRenderer CellRendererGetterDelegate(Object rowObject, OLVColumn column);
|
||||
|
||||
/// <summary>
|
||||
/// The callbacks for RightColumnClick events
|
||||
/// </summary>
|
||||
public delegate void ColumnRightClickEventHandler(object sender, ColumnClickEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// This delegate will be used to own draw header column.
|
||||
/// </summary>
|
||||
public delegate bool HeaderDrawingDelegate(Graphics g, Rectangle r, int columnIndex, OLVColumn column, bool isPressed, HeaderStateStyle stateStyle);
|
||||
|
||||
/// <summary>
|
||||
/// This delegate is called when a group has been created but not yet made
|
||||
/// into a real ListViewGroup. The user can take this opportunity to fill
|
||||
/// in lots of other details about the group.
|
||||
/// </summary>
|
||||
public delegate void GroupFormatterDelegate(OLVGroup group, GroupingParameters parms);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to retrieve the object that is the key of the group to which the given row belongs.
|
||||
/// </summary>
|
||||
public delegate Object GroupKeyGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to convert a group key into a title for the group
|
||||
/// </summary>
|
||||
public delegate string GroupKeyToTitleConverterDelegate(Object groupKey);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the tooltip for a column header
|
||||
/// </summary>
|
||||
public delegate String HeaderToolTipGetterDelegate(OLVColumn column);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to fetch the image selector that should be used
|
||||
/// to choose an image for this column.
|
||||
/// </summary>
|
||||
public delegate Object ImageGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to draw a cell
|
||||
/// </summary>
|
||||
public delegate bool RenderDelegate(EventArgs e, Graphics g, Rectangle r, Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to fetch a row object for virtual lists
|
||||
/// </summary>
|
||||
public delegate Object RowGetterDelegate(int rowIndex);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to format a listviewitem before it is added to the control.
|
||||
/// </summary>
|
||||
public delegate void RowFormatterDelegate(OLVListItem olvItem);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates can be used to return the array of texts that should be searched for text filtering
|
||||
/// </summary>
|
||||
public delegate string[] SearchValueGetterDelegate(Object value);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to sort the listview in some custom fashion
|
||||
/// </summary>
|
||||
public delegate void SortDelegate(OLVColumn column, SortOrder sortOrder);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to order two strings.
|
||||
/// x cannot be null. y can be null.
|
||||
/// </summary>
|
||||
public delegate int StringCompareDelegate(string x, string y);
|
||||
|
||||
#endregion
|
||||
}
|
||||
407
ObjectListView/Implementation/DragSource.cs
Normal file
@@ -0,0 +1,407 @@
|
||||
///*
|
||||
// * DragSource.cs - Add drag source functionality to an ObjectListView
|
||||
// *
|
||||
// * UNFINISHED
|
||||
// *
|
||||
// * Author: Phillip Piper
|
||||
// * Date: 2009-03-17 5:15 PM
|
||||
// *
|
||||
// * Change log:
|
||||
// * v2.3
|
||||
// * 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
|
||||
// * (since MS didn't make it part of the 'All' value)
|
||||
// * v2.2
|
||||
// * 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
|
||||
// * 2009-03-17 JPP - Initial version
|
||||
// *
|
||||
// * Copyright (C) 2009 Phillip Piper
|
||||
// *
|
||||
// * This program is free software: you can redistribute it and/or modify
|
||||
// * it under the terms of the GNU General Public License as published by
|
||||
// * the Free Software Foundation, either version 3 of the License, or
|
||||
// * (at your option) any later version.
|
||||
// *
|
||||
// * This program is distributed in the hope that it will be useful,
|
||||
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// * GNU General Public License for more details.
|
||||
// *
|
||||
// * You should have received a copy of the GNU General Public License
|
||||
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// *
|
||||
// * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
// */
|
||||
|
||||
//using System;
|
||||
//using System.Collections;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Text;
|
||||
//using System.Windows.Forms;
|
||||
//using System.Drawing;
|
||||
//using System.Drawing.Drawing2D;
|
||||
|
||||
//namespace BrightIdeasSoftware
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// An IDragSource controls how drag out from the ObjectListView will behave
|
||||
// /// </summary>
|
||||
// public interface IDragSource
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// A drag operation is beginning. Return the data object that will be used
|
||||
// /// for data transfer. Return null to prevent the drag from starting.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// The returned object is later passed to the GetAllowedEffect() and EndDrag()
|
||||
// /// methods.
|
||||
// /// </remarks>
|
||||
// /// <param name="olv">What ObjectListView is being dragged from.</param>
|
||||
// /// <param name="button">Which mouse button is down?</param>
|
||||
// /// <param name="item">What item was directly dragged by the user? There may be more than just this
|
||||
// /// item selected.</param>
|
||||
// /// <returns>The data object that will be used for data transfer. This will often be a subclass
|
||||
// /// of DataObject, but does not need to be.</returns>
|
||||
// Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item);
|
||||
|
||||
// /// <summary>
|
||||
// /// What operations are possible for this drag? This controls the icon shown during the drag
|
||||
// /// </summary>
|
||||
// /// <param name="dragObject">The data object returned by StartDrag()</param>
|
||||
// /// <returns>A combination of DragDropEffects flags</returns>
|
||||
// DragDropEffects GetAllowedEffects(Object dragObject);
|
||||
|
||||
// /// <summary>
|
||||
// /// The drag operation is complete. Do whatever is necessary to complete the action.
|
||||
// /// </summary>
|
||||
// /// <param name="dragObject">The data object returned by StartDrag()</param>
|
||||
// /// <param name="effect">The value returned from GetAllowedEffects()</param>
|
||||
// void EndDrag(Object dragObject, DragDropEffects effect);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// A do-nothing implementation of IDragSource that can be safely subclassed.
|
||||
// /// </summary>
|
||||
// public class AbstractDragSource : IDragSource
|
||||
// {
|
||||
// #region IDragSource Members
|
||||
|
||||
// /// <summary>
|
||||
// /// See IDragSource documentation
|
||||
// /// </summary>
|
||||
// /// <param name="olv"></param>
|
||||
// /// <param name="button"></param>
|
||||
// /// <param name="item"></param>
|
||||
// /// <returns></returns>
|
||||
// public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// See IDragSource documentation
|
||||
// /// </summary>
|
||||
// /// <param name="data"></param>
|
||||
// /// <returns></returns>
|
||||
// public virtual DragDropEffects GetAllowedEffects(Object data) {
|
||||
// return DragDropEffects.None;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// See IDragSource documentation
|
||||
// /// </summary>
|
||||
// /// <param name="dragObject"></param>
|
||||
// /// <param name="effect"></param>
|
||||
// public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// A reasonable implementation of IDragSource that provides normal
|
||||
// /// drag source functionality. It creates a data object that supports
|
||||
// /// inter-application dragging of text and HTML representation of
|
||||
// /// the dragged rows. It can optionally force a refresh of all dragged
|
||||
// /// rows when the drag is complete.
|
||||
// /// </summary>
|
||||
// /// <remarks>Subclasses can override GetDataObject() to add new
|
||||
// /// data formats to the data transfer object.</remarks>
|
||||
// public class SimpleDragSource : IDragSource
|
||||
// {
|
||||
// #region Constructors
|
||||
|
||||
// /// <summary>
|
||||
// /// Construct a SimpleDragSource
|
||||
// /// </summary>
|
||||
// public SimpleDragSource() {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Construct a SimpleDragSource that refreshes the dragged rows when
|
||||
// /// the drag is complete
|
||||
// /// </summary>
|
||||
// /// <param name="refreshAfterDrop"></param>
|
||||
// public SimpleDragSource(bool refreshAfterDrop) {
|
||||
// this.RefreshAfterDrop = refreshAfterDrop;
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Public properties
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets or sets whether the dragged rows should be refreshed when the
|
||||
// /// drag operation is complete.
|
||||
// /// </summary>
|
||||
// public bool RefreshAfterDrop {
|
||||
// get { return refreshAfterDrop; }
|
||||
// set { refreshAfterDrop = value; }
|
||||
// }
|
||||
// private bool refreshAfterDrop;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region IDragSource Members
|
||||
|
||||
// /// <summary>
|
||||
// /// Create a DataObject when the user does a left mouse drag operation.
|
||||
// /// See IDragSource for further information.
|
||||
// /// </summary>
|
||||
// /// <param name="olv"></param>
|
||||
// /// <param name="button"></param>
|
||||
// /// <param name="item"></param>
|
||||
// /// <returns></returns>
|
||||
// public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
|
||||
// // We only drag on left mouse
|
||||
// if (button != MouseButtons.Left)
|
||||
// return null;
|
||||
|
||||
// return this.CreateDataObject(olv);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Which operations are allowed in the operation? By default, all operations are supported.
|
||||
// /// </summary>
|
||||
// /// <param name="data"></param>
|
||||
// /// <returns>All opertions are supported</returns>
|
||||
// public virtual DragDropEffects GetAllowedEffects(Object data) {
|
||||
// return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'??
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// The drag operation is finished. Refreshe the dragged rows if so configured.
|
||||
// /// </summary>
|
||||
// /// <param name="dragObject"></param>
|
||||
// /// <param name="effect"></param>
|
||||
// public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
|
||||
// OLVDataObject data = dragObject as OLVDataObject;
|
||||
// if (data == null)
|
||||
// return;
|
||||
|
||||
// if (this.RefreshAfterDrop)
|
||||
// data.ListView.RefreshObjects(data.ModelObjects);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Create a data object that will be used to as the data object
|
||||
// /// for the drag operation.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Subclasses can override this method add new formats to the data object.
|
||||
// /// </remarks>
|
||||
// /// <param name="olv">The ObjectListView that is the source of the drag</param>
|
||||
// /// <returns>A data object for the drag</returns>
|
||||
// protected virtual object CreateDataObject(ObjectListView olv) {
|
||||
// OLVDataObject data = new OLVDataObject(olv);
|
||||
// data.CreateTextFormats();
|
||||
// return data;
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// A data transfer object that knows how to transform a list of model
|
||||
// /// objects into a text and HTML representation.
|
||||
// /// </summary>
|
||||
// public class OLVDataObject : DataObject
|
||||
// {
|
||||
// #region Life and death
|
||||
|
||||
// /// <summary>
|
||||
// /// Create a data object from the selected objects in the given ObjectListView
|
||||
// /// </summary>
|
||||
// /// <param name="olv">The source of the data object</param>
|
||||
// public OLVDataObject(ObjectListView olv) : this(olv, olv.SelectedObjects) {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Create a data object which operates on the given model objects
|
||||
// /// in the given ObjectListView
|
||||
// /// </summary>
|
||||
// /// <param name="olv">The source of the data object</param>
|
||||
// /// <param name="modelObjects">The model objects to be put into the data object</param>
|
||||
// public OLVDataObject(ObjectListView olv, IList modelObjects) {
|
||||
// this.objectListView = olv;
|
||||
// this.modelObjects = modelObjects;
|
||||
// this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer;
|
||||
// this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy;
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Properties
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets or sets whether hidden columns will also be included in the text
|
||||
// /// and HTML representation. If this is false, only visible columns will
|
||||
// /// be included.
|
||||
// /// </summary>
|
||||
// public bool IncludeHiddenColumns {
|
||||
// get { return includeHiddenColumns; }
|
||||
// }
|
||||
// private bool includeHiddenColumns;
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets or sets whether column headers will also be included in the text
|
||||
// /// and HTML representation.
|
||||
// /// </summary>
|
||||
// public bool IncludeColumnHeaders
|
||||
// {
|
||||
// get { return includeColumnHeaders; }
|
||||
// }
|
||||
// private bool includeColumnHeaders;
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets the ObjectListView that is being used as the source of the data
|
||||
// /// </summary>
|
||||
// public ObjectListView ListView {
|
||||
// get { return objectListView; }
|
||||
// }
|
||||
// private ObjectListView objectListView;
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets the model objects that are to be placed in the data object
|
||||
// /// </summary>
|
||||
// public IList ModelObjects {
|
||||
// get { return modelObjects; }
|
||||
// }
|
||||
// private IList modelObjects = new ArrayList();
|
||||
|
||||
// #endregion
|
||||
|
||||
// /// <summary>
|
||||
// /// Put a text and HTML representation of our model objects
|
||||
// /// into the data object.
|
||||
// /// </summary>
|
||||
// public void CreateTextFormats() {
|
||||
// IList<OLVColumn> columns = this.IncludeHiddenColumns ? this.ListView.AllColumns : this.ListView.ColumnsInDisplayOrder;
|
||||
|
||||
// // Build text and html versions of the selection
|
||||
// StringBuilder sbText = new StringBuilder();
|
||||
// StringBuilder sbHtml = new StringBuilder("<table>");
|
||||
|
||||
// // Include column headers
|
||||
// if (includeColumnHeaders)
|
||||
// {
|
||||
// sbHtml.Append("<tr><td>");
|
||||
// foreach (OLVColumn col in columns)
|
||||
// {
|
||||
// if (col != columns[0])
|
||||
// {
|
||||
// sbText.Append("\t");
|
||||
// sbHtml.Append("</td><td>");
|
||||
// }
|
||||
// string strValue = col.Text;
|
||||
// sbText.Append(strValue);
|
||||
// sbHtml.Append(strValue); //TODO: Should encode the string value
|
||||
// }
|
||||
// sbText.AppendLine();
|
||||
// sbHtml.AppendLine("</td></tr>");
|
||||
// }
|
||||
|
||||
// foreach (object modelObject in this.ModelObjects)
|
||||
// {
|
||||
// sbHtml.Append("<tr><td>");
|
||||
// foreach (OLVColumn col in columns) {
|
||||
// if (col != columns[0]) {
|
||||
// sbText.Append("\t");
|
||||
// sbHtml.Append("</td><td>");
|
||||
// }
|
||||
// string strValue = col.GetStringValue(modelObject);
|
||||
// sbText.Append(strValue);
|
||||
// sbHtml.Append(strValue); //TODO: Should encode the string value
|
||||
// }
|
||||
// sbText.AppendLine();
|
||||
// sbHtml.AppendLine("</td></tr>");
|
||||
// }
|
||||
// sbHtml.AppendLine("</table>");
|
||||
|
||||
// // Put both the text and html versions onto the clipboard.
|
||||
// // For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
|
||||
// // but using SetData() does.
|
||||
// //this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
|
||||
// this.SetData(sbText.ToString());
|
||||
// this.SetText(ConvertToHtmlFragment(sbHtml.ToString()), TextDataFormat.Html);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Make a HTML representation of our model objects
|
||||
// /// </summary>
|
||||
// public string CreateHtml() {
|
||||
// IList<OLVColumn> columns = this.ListView.ColumnsInDisplayOrder;
|
||||
|
||||
// // Build html version of the selection
|
||||
// StringBuilder sbHtml = new StringBuilder("<table>");
|
||||
|
||||
// foreach (object modelObject in this.ModelObjects) {
|
||||
// sbHtml.Append("<tr><td>");
|
||||
// foreach (OLVColumn col in columns) {
|
||||
// if (col != columns[0]) {
|
||||
// sbHtml.Append("</td><td>");
|
||||
// }
|
||||
// string strValue = col.GetStringValue(modelObject);
|
||||
// sbHtml.Append(strValue); //TODO: Should encode the string value
|
||||
// }
|
||||
// sbHtml.AppendLine("</td></tr>");
|
||||
// }
|
||||
// sbHtml.AppendLine("</table>");
|
||||
|
||||
// return sbHtml.ToString();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Convert the fragment of HTML into the Clipboards HTML format.
|
||||
// /// </summary>
|
||||
// /// <remarks>The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx
|
||||
// /// </remarks>
|
||||
// /// <param name="fragment">The HTML to put onto the clipboard. It must be valid HTML!</param>
|
||||
// /// <returns>A string that can be put onto the clipboard and will be recognized as HTML</returns>
|
||||
// private string ConvertToHtmlFragment(string fragment) {
|
||||
// // Minimal implementation of HTML clipboard format
|
||||
// string source = "http://www.codeproject.com/KB/list/ObjectListView.aspx";
|
||||
|
||||
// const String MARKER_BLOCK =
|
||||
// "Version:1.0\r\n" +
|
||||
// "StartHTML:{0,8}\r\n" +
|
||||
// "EndHTML:{1,8}\r\n" +
|
||||
// "StartFragment:{2,8}\r\n" +
|
||||
// "EndFragment:{3,8}\r\n" +
|
||||
// "StartSelection:{2,8}\r\n" +
|
||||
// "EndSelection:{3,8}\r\n" +
|
||||
// "SourceURL:{4}\r\n" +
|
||||
// "{5}";
|
||||
|
||||
// int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, source, "").Length;
|
||||
|
||||
// const String DEFAULT_HTML_BODY =
|
||||
// "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
|
||||
// "<HTML><HEAD></HEAD><BODY><!--StartFragment-->{0}<!--EndFragment--></BODY></HTML>";
|
||||
|
||||
// string html = String.Format(DEFAULT_HTML_BODY, fragment);
|
||||
// int startFragment = prefixLength + html.IndexOf(fragment);
|
||||
// int endFragment = startFragment + fragment.Length;
|
||||
|
||||
// return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, source, html);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
1398
ObjectListView/Implementation/DropSink.cs
Normal file
104
ObjectListView/Implementation/Enums.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Enums - All enum definitions used in ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
public partial class ObjectListView {
|
||||
/// <summary>
|
||||
/// How does a user indicate that they want to edit cells?
|
||||
/// </summary>
|
||||
public enum CellEditActivateMode {
|
||||
/// <summary>
|
||||
/// This list cannot be edited. F2 does nothing.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A single click on a <strong>subitem</strong> will edit the value. Single clicking the primary column,
|
||||
/// selects the row just like normal. The user must press F2 to edit the primary column.
|
||||
/// </summary>
|
||||
SingleClick = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Double clicking a subitem or the primary column will edit that cell.
|
||||
/// F2 will edit the primary column.
|
||||
/// </summary>
|
||||
DoubleClick = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Pressing F2 is the only way to edit the cells. Once the primary column is being edited,
|
||||
/// the other cells in the row can be edited by pressing Tab.
|
||||
/// </summary>
|
||||
F2Only = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A single click on a <strong>any</strong> cell will edit the value, even the primary column.
|
||||
/// </summary>
|
||||
SingleClickAlways = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These values specify how column selection will be presented to the user
|
||||
/// </summary>
|
||||
public enum ColumnSelectBehaviour {
|
||||
/// <summary>
|
||||
/// No column selection will be presented
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The columns will be show in the main menu
|
||||
/// </summary>
|
||||
InlineMenu,
|
||||
|
||||
/// <summary>
|
||||
/// The columns will be shown in a submenu
|
||||
/// </summary>
|
||||
Submenu,
|
||||
|
||||
/// <summary>
|
||||
/// A model dialog will be presented to allow the user to choose columns
|
||||
/// </summary>
|
||||
ModelDialog,
|
||||
|
||||
/*
|
||||
* NonModelDialog is just a little bit tricky since the OLV can change views while the dialog is showing
|
||||
* So, just comment this out for the time being.
|
||||
|
||||
/// <summary>
|
||||
/// A non-model dialog will be presented to allow the user to choose columns
|
||||
/// </summary>
|
||||
NonModelDialog
|
||||
*
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
2425
ObjectListView/Implementation/Events.cs
Normal file
175
ObjectListView/Implementation/GroupingParameters.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* GroupingParameters - All the data that is used to create groups in an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// This class contains all the settings used when groups are created
|
||||
/// </summary>
|
||||
public class GroupingParameters {
|
||||
/// <summary>
|
||||
/// Create a GroupingParameters
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="groupByColumn"></param>
|
||||
/// <param name="groupByOrder"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="order"></param>
|
||||
/// <param name="secondaryColumn"></param>
|
||||
/// <param name="secondaryOrder"></param>
|
||||
/// <param name="titleFormat"></param>
|
||||
/// <param name="titleSingularFormat"></param>
|
||||
/// <param name="sortItemsByPrimaryColumn"></param>
|
||||
public GroupingParameters(ObjectListView olv, OLVColumn groupByColumn, SortOrder groupByOrder,
|
||||
OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder,
|
||||
string titleFormat, string titleSingularFormat, bool sortItemsByPrimaryColumn) {
|
||||
this.ListView = olv;
|
||||
this.GroupByColumn = groupByColumn;
|
||||
this.GroupByOrder = groupByOrder;
|
||||
this.PrimarySort = column;
|
||||
this.PrimarySortOrder = order;
|
||||
this.SecondarySort = secondaryColumn;
|
||||
this.SecondarySortOrder = secondaryOrder;
|
||||
this.SortItemsByPrimaryColumn = sortItemsByPrimaryColumn;
|
||||
this.TitleFormat = titleFormat;
|
||||
this.TitleSingularFormat = titleSingularFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ObjectListView being grouped
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return this.listView; }
|
||||
set { this.listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column used to create groups
|
||||
/// </summary>
|
||||
public OLVColumn GroupByColumn {
|
||||
get { return this.groupByColumn; }
|
||||
set { this.groupByColumn = value; }
|
||||
}
|
||||
private OLVColumn groupByColumn;
|
||||
|
||||
/// <summary>
|
||||
/// In what order will the groups themselves be sorted?
|
||||
/// </summary>
|
||||
public SortOrder GroupByOrder {
|
||||
get { return this.groupByOrder; }
|
||||
set { this.groupByOrder = value; }
|
||||
}
|
||||
private SortOrder groupByOrder;
|
||||
|
||||
/// <summary>
|
||||
/// If this is set, this comparer will be used to order the groups
|
||||
/// </summary>
|
||||
public IComparer<OLVGroup> GroupComparer {
|
||||
get { return this.groupComparer; }
|
||||
set { this.groupComparer = value; }
|
||||
}
|
||||
private IComparer<OLVGroup> groupComparer;
|
||||
|
||||
/// <summary>
|
||||
/// If this is set, this comparer will be used to order items within each group
|
||||
/// </summary>
|
||||
public IComparer<OLVListItem> ItemComparer {
|
||||
get { return this.itemComparer; }
|
||||
set { this.itemComparer = value; }
|
||||
}
|
||||
private IComparer<OLVListItem> itemComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column that will be the primary sort
|
||||
/// </summary>
|
||||
public OLVColumn PrimarySort {
|
||||
get { return this.primarySort; }
|
||||
set { this.primarySort = value; }
|
||||
}
|
||||
private OLVColumn primarySort;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ordering for the primary sort
|
||||
/// </summary>
|
||||
public SortOrder PrimarySortOrder {
|
||||
get { return this.primarySortOrder; }
|
||||
set { this.primarySortOrder = value; }
|
||||
}
|
||||
private SortOrder primarySortOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column used for secondary sorting
|
||||
/// </summary>
|
||||
public OLVColumn SecondarySort {
|
||||
get { return this.secondarySort; }
|
||||
set { this.secondarySort = value; }
|
||||
}
|
||||
private OLVColumn secondarySort;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ordering for the secondary sort
|
||||
/// </summary>
|
||||
public SortOrder SecondarySortOrder {
|
||||
get { return this.secondarySortOrder; }
|
||||
set { this.secondarySortOrder = value; }
|
||||
}
|
||||
private SortOrder secondarySortOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title format used for groups with zero or more than one element
|
||||
/// </summary>
|
||||
public string TitleFormat {
|
||||
get { return this.titleFormat; }
|
||||
set { this.titleFormat = value; }
|
||||
}
|
||||
private string titleFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title format used for groups with only one element
|
||||
/// </summary>
|
||||
public string TitleSingularFormat {
|
||||
get { return this.titleSingularFormat; }
|
||||
set { this.titleSingularFormat = value; }
|
||||
}
|
||||
private string titleSingularFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the items should be sorted by the primary column
|
||||
/// </summary>
|
||||
public bool SortItemsByPrimaryColumn {
|
||||
get { return this.sortItemsByPrimaryColumn; }
|
||||
set { this.sortItemsByPrimaryColumn = value; }
|
||||
}
|
||||
private bool sortItemsByPrimaryColumn;
|
||||
}
|
||||
}
|
||||
747
ObjectListView/Implementation/Groups.cs
Normal file
@@ -0,0 +1,747 @@
|
||||
/*
|
||||
* Groups - Enhancements to the normal ListViewGroup
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 22/08/2009 6:03PM
|
||||
*
|
||||
* Change log:
|
||||
* v2.3
|
||||
* 2009-09-09 JPP - Added Collapsed and Collapsible properties
|
||||
* 2009-09-01 JPP - Cleaned up code, added more docs
|
||||
* - Works under VS2005 again
|
||||
* 2009-08-22 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
* - Implement subseting
|
||||
* - Implement footer items
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// These values indicate what is the state of the group. These values
|
||||
/// are taken directly from the SDK and many are not used by ObjectListView.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GroupState
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal
|
||||
/// </summary>
|
||||
LVGS_NORMAL = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// Collapsed
|
||||
/// </summary>
|
||||
LVGS_COLLAPSED = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// Hidden
|
||||
/// </summary>
|
||||
LVGS_HIDDEN = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// NoHeader
|
||||
/// </summary>
|
||||
LVGS_NOHEADER = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// Can be collapsed
|
||||
/// </summary>
|
||||
LVGS_COLLAPSIBLE = 0x8,
|
||||
|
||||
/// <summary>
|
||||
/// Has focus
|
||||
/// </summary>
|
||||
LVGS_FOCUSED = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Is Selected
|
||||
/// </summary>
|
||||
LVGS_SELECTED = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// Is subsetted
|
||||
/// </summary>
|
||||
LVGS_SUBSETED = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Subset link has focus
|
||||
/// </summary>
|
||||
LVGS_SUBSETLINKFOCUSED = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// All styles
|
||||
/// </summary>
|
||||
LVGS_ALL = 0xFFFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This mask indicates which members of a LVGROUP have valid data. These values
|
||||
/// are taken directly from the SDK and many are not used by ObjectListView.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GroupMask
|
||||
{
|
||||
/// <summary>
|
||||
/// No mask
|
||||
/// </summary>
|
||||
LVGF_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Group has header
|
||||
/// </summary>
|
||||
LVGF_HEADER = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Group has footer
|
||||
/// </summary>
|
||||
LVGF_FOOTER = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Group has state
|
||||
/// </summary>
|
||||
LVGF_STATE = 4,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGF_ALIGN = 8,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGF_GROUPID = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// pszSubtitle is valid
|
||||
/// </summary>
|
||||
LVGF_SUBTITLE = 0x00100,
|
||||
|
||||
/// <summary>
|
||||
/// pszTask is valid
|
||||
/// </summary>
|
||||
LVGF_TASK = 0x00200,
|
||||
|
||||
/// <summary>
|
||||
/// pszDescriptionTop is valid
|
||||
/// </summary>
|
||||
LVGF_DESCRIPTIONTOP = 0x00400,
|
||||
|
||||
/// <summary>
|
||||
/// pszDescriptionBottom is valid
|
||||
/// </summary>
|
||||
LVGF_DESCRIPTIONBOTTOM = 0x00800,
|
||||
|
||||
/// <summary>
|
||||
/// iTitleImage is valid
|
||||
/// </summary>
|
||||
LVGF_TITLEIMAGE = 0x01000,
|
||||
|
||||
/// <summary>
|
||||
/// iExtendedImage is valid
|
||||
/// </summary>
|
||||
LVGF_EXTENDEDIMAGE = 0x02000,
|
||||
|
||||
/// <summary>
|
||||
/// iFirstItem and cItems are valid
|
||||
/// </summary>
|
||||
LVGF_ITEMS = 0x04000,
|
||||
|
||||
/// <summary>
|
||||
/// pszSubsetTitle is valid
|
||||
/// </summary>
|
||||
LVGF_SUBSET = 0x08000,
|
||||
|
||||
/// <summary>
|
||||
/// readonly, cItems holds count of items in visible subset, iFirstItem is valid
|
||||
/// </summary>
|
||||
LVGF_SUBSETITEMS = 0x10000
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This mask indicates which members of a GROUPMETRICS structure are valid
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GroupMetricsMask
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_BORDERSIZE = 1,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_BORDERCOLOR = 2,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_TEXTCOLOR = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class enhance the capabilities of a normal ListViewGroup,
|
||||
/// enabling the functionality that was released in v6 of the common controls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In this implementation (2009-09), these objects are essentially passive.
|
||||
/// Setting properties does not automatically change the associated group in
|
||||
/// the listview. Collapsed and Collapsible are two exceptions to this and
|
||||
/// give immediate results.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This really should be a subclass of ListViewGroup, but that class is
|
||||
/// sealed (why is that?). So this class provides the same interface as a
|
||||
/// ListViewGroup, plus many other new properties.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class OLVGroup
|
||||
{
|
||||
#region Creation
|
||||
|
||||
/// <summary>
|
||||
/// Create an OLVGroup
|
||||
/// </summary>
|
||||
public OLVGroup() : this("Default group header") {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a group with the given title
|
||||
/// </summary>
|
||||
/// <param name="header">Title of the group</param>
|
||||
public OLVGroup(string header) {
|
||||
this.Header = header;
|
||||
this.Id = OLVGroup.nextId++;
|
||||
this.TitleImage = -1;
|
||||
this.ExtendedImage = -1;
|
||||
}
|
||||
private static int nextId;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bottom description of the group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Descriptions only appear when group is centered and there is a title image
|
||||
/// </remarks>
|
||||
public string BottomDescription {
|
||||
get { return this.bottomDescription; }
|
||||
set { this.bottomDescription = value; }
|
||||
}
|
||||
private string bottomDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not this group is collapsed
|
||||
/// </summary>
|
||||
public bool Collapsed {
|
||||
get { return this.GetOneState(GroupState.LVGS_COLLAPSED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_COLLAPSED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not this group can be collapsed
|
||||
/// </summary>
|
||||
public bool Collapsible {
|
||||
get { return this.GetOneState(GroupState.LVGS_COLLAPSIBLE); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_COLLAPSIBLE); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets some representation of the contents of this group
|
||||
/// </summary>
|
||||
/// <remarks>This is user defined (like Tag)</remarks>
|
||||
public IList Contents {
|
||||
get { return this.contents; }
|
||||
set { this.contents = value; }
|
||||
}
|
||||
private IList contents;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this group has been created.
|
||||
/// </summary>
|
||||
public bool Created {
|
||||
get { return this.ListView != null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the int or string that will select the extended image to be shown against the title
|
||||
/// </summary>
|
||||
public object ExtendedImage {
|
||||
get { return this.extendedImage; }
|
||||
set { this.extendedImage = value; }
|
||||
}
|
||||
private object extendedImage;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the footer of the group
|
||||
/// </summary>
|
||||
public string Footer {
|
||||
get { return this.footer; }
|
||||
set { this.footer = value; }
|
||||
}
|
||||
private string footer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal id of our associated ListViewGroup.
|
||||
/// </summary>
|
||||
public int GroupId {
|
||||
get {
|
||||
if (this.ListViewGroup == null)
|
||||
return this.Id;
|
||||
|
||||
// Use reflection to get around the access control on the ID property
|
||||
if (OLVGroup.groupIdPropInfo == null) {
|
||||
OLVGroup.groupIdPropInfo = typeof(ListViewGroup).GetProperty("ID",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
System.Diagnostics.Debug.Assert(OLVGroup.groupIdPropInfo != null);
|
||||
}
|
||||
|
||||
int? groupId = OLVGroup.groupIdPropInfo.GetValue(this.ListViewGroup, null) as int?;
|
||||
return groupId.HasValue ? groupId.Value : -1;
|
||||
}
|
||||
}
|
||||
private static PropertyInfo groupIdPropInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the header of the group
|
||||
/// </summary>
|
||||
public string Header {
|
||||
get { return this.header; }
|
||||
set { this.header = value; }
|
||||
}
|
||||
private string header;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the horizontal alignment of the group header
|
||||
/// </summary>
|
||||
public HorizontalAlignment HeaderAlignment {
|
||||
get { return this.headerAlignment; }
|
||||
set { this.headerAlignment = value; }
|
||||
}
|
||||
private HorizontalAlignment headerAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the internally created id of the group
|
||||
/// </summary>
|
||||
public int Id {
|
||||
get { return this.id; }
|
||||
set { this.id = value; }
|
||||
}
|
||||
private int id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets ListViewItems that are members of this group
|
||||
/// </summary>
|
||||
/// <remarks>Listener of the BeforeCreatingGroups event can populate this collection.
|
||||
/// It is only used on non-virtual lists.</remarks>
|
||||
public IList<OLVListItem> Items {
|
||||
get { return this.items; }
|
||||
set { this.items = value; }
|
||||
}
|
||||
private IList<OLVListItem> items = new List<OLVListItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key that was used to partition objects into this group
|
||||
/// </summary>
|
||||
/// <remarks>This is user defined (like Tag)</remarks>
|
||||
public object Key {
|
||||
get { return this.key; }
|
||||
set { this.key = value; }
|
||||
}
|
||||
private object key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that this group belongs to
|
||||
/// </summary>
|
||||
/// <remarks>If this is null, the group has not yet been created.</remarks>
|
||||
public ObjectListView ListView {
|
||||
get { return this.listView; }
|
||||
protected set { this.listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the group
|
||||
/// </summary>
|
||||
/// <remarks>As of 2009-09-01, this property is not used.</remarks>
|
||||
public string Name {
|
||||
get { return this.name; }
|
||||
set { this.name = value; }
|
||||
}
|
||||
private string name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this group is focused
|
||||
/// </summary>
|
||||
public bool Focused
|
||||
{
|
||||
get { return this.GetOneState(GroupState.LVGS_FOCUSED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_FOCUSED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this group is selected
|
||||
/// </summary>
|
||||
public bool Selected
|
||||
{
|
||||
get { return this.GetOneState(GroupState.LVGS_SELECTED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_SELECTED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text that will show that this group is subsetted
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As of WinSDK v7.0, subsetting of group is officially unimplemented.
|
||||
/// We can get around this using undocumented interfaces and may do so.
|
||||
/// </remarks>
|
||||
public string SubsetTitle {
|
||||
get { return this.subsetTitle; }
|
||||
set { this.subsetTitle = value; }
|
||||
}
|
||||
private string subsetTitle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the subtitleof the task
|
||||
/// </summary>
|
||||
public string Subtitle {
|
||||
get { return this.subtitle; }
|
||||
set { this.subtitle = value; }
|
||||
}
|
||||
private string subtitle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value by which this group will be sorted.
|
||||
/// </summary>
|
||||
public IComparable SortValue {
|
||||
get { return this.sortValue; }
|
||||
set { this.sortValue = value; }
|
||||
}
|
||||
private IComparable sortValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state of the group
|
||||
/// </summary>
|
||||
public GroupState State {
|
||||
get { return this.state; }
|
||||
set { this.state = value; }
|
||||
}
|
||||
private GroupState state;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which bits of State are valid
|
||||
/// </summary>
|
||||
public GroupState StateMask {
|
||||
get { return this.stateMask; }
|
||||
set { this.stateMask = value; }
|
||||
}
|
||||
private GroupState stateMask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this group is showing only a subset of its elements
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As of WinSDK v7.0, this property officially does nothing.
|
||||
/// </remarks>
|
||||
public bool Subseted {
|
||||
get { return this.GetOneState(GroupState.LVGS_SUBSETED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_SUBSETED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user-defined data attached to this group
|
||||
/// </summary>
|
||||
public object Tag {
|
||||
get { return this.tag; }
|
||||
set { this.tag = value; }
|
||||
}
|
||||
private object tag;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the task of this group
|
||||
/// </summary>
|
||||
/// <remarks>This task is the clickable text that appears on the right margin
|
||||
/// of the group header.</remarks>
|
||||
public string Task {
|
||||
get { return this.task; }
|
||||
set { this.task = value; }
|
||||
}
|
||||
private string task;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the int or string that will select the image to be shown against the title
|
||||
/// </summary>
|
||||
public object TitleImage {
|
||||
get { return this.titleImage; }
|
||||
set { this.titleImage = value; }
|
||||
}
|
||||
private object titleImage;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the top description of the group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Descriptions only appear when group is centered and there is a title image
|
||||
/// </remarks>
|
||||
public string TopDescription {
|
||||
get { return this.topDescription; }
|
||||
set { this.topDescription = value; }
|
||||
}
|
||||
private string topDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of items that are within this group.
|
||||
/// </summary>
|
||||
/// <remarks>This should only be used for virtual groups.</remarks>
|
||||
public int VirtualItemCount {
|
||||
get { return this.virtualItemCount; }
|
||||
set { this.virtualItemCount = value; }
|
||||
}
|
||||
private int virtualItemCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ListViewGroup that is shadowed by this group.
|
||||
/// </summary>
|
||||
/// <remarks>For virtual groups, this will always be null.</remarks>
|
||||
protected ListViewGroup ListViewGroup {
|
||||
get { return this.listViewGroup; }
|
||||
set { this.listViewGroup = value; }
|
||||
}
|
||||
private ListViewGroup listViewGroup;
|
||||
#endregion
|
||||
|
||||
#region Calculations/Conversions
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the index into the group image list of the given image selector
|
||||
/// </summary>
|
||||
/// <param name="imageSelector"></param>
|
||||
/// <returns></returns>
|
||||
public int GetImageIndex(object imageSelector) {
|
||||
if (imageSelector == null || this.ListView == null || this.ListView.GroupImageList == null)
|
||||
return -1;
|
||||
|
||||
if (imageSelector is Int32)
|
||||
return (int)imageSelector;
|
||||
|
||||
String imageSelectorAsString = imageSelector as String;
|
||||
if (imageSelectorAsString != null)
|
||||
return this.ListView.GroupImageList.Images.IndexOfKey(imageSelectorAsString);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert this object to a string representation
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString() {
|
||||
return this.Header;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Insert a native group into the underlying Windows control,
|
||||
/// *without* using a ListViewGroup
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <remarks>This is used when creating virtual groups</remarks>
|
||||
public void InsertGroupNewStyle(ObjectListView olv) {
|
||||
this.ListView = olv;
|
||||
NativeMethods.InsertGroup(olv, this.AsNativeGroup(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a native group into the underlying control via a ListViewGroup
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
public void InsertGroupOldStyle(ObjectListView olv) {
|
||||
this.ListView = olv;
|
||||
|
||||
// Create/update the associated ListViewGroup
|
||||
if (this.ListViewGroup == null)
|
||||
this.ListViewGroup = new ListViewGroup();
|
||||
this.ListViewGroup.Header = this.Header;
|
||||
this.ListViewGroup.HeaderAlignment = this.HeaderAlignment;
|
||||
this.ListViewGroup.Name = this.Name;
|
||||
|
||||
// Remember which OLVGroup created the ListViewGroup
|
||||
this.ListViewGroup.Tag = this;
|
||||
|
||||
// Add the group to the control
|
||||
olv.Groups.Add(this.ListViewGroup);
|
||||
|
||||
// Add any extra information
|
||||
NativeMethods.SetGroupInfo(olv, this.GroupId, this.AsNativeGroup(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the members of the group to match the current contents of Items,
|
||||
/// using a ListViewGroup
|
||||
/// </summary>
|
||||
public void SetItemsOldStyle() {
|
||||
List<OLVListItem> list = this.Items as List<OLVListItem>;
|
||||
if (list == null) {
|
||||
foreach (OLVListItem item in this.Items) {
|
||||
this.ListViewGroup.Items.Add(item);
|
||||
}
|
||||
} else {
|
||||
this.ListViewGroup.Items.AddRange(list.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Create a native LVGROUP structure that matches this group
|
||||
/// </summary>
|
||||
internal NativeMethods.LVGROUP2 AsNativeGroup(bool withId) {
|
||||
|
||||
NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2();
|
||||
group.cbSize = (uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2));
|
||||
group.mask = (uint)(GroupMask.LVGF_HEADER ^ GroupMask.LVGF_ALIGN ^ GroupMask.LVGF_STATE);
|
||||
group.pszHeader = this.Header;
|
||||
group.uAlign = (uint)this.HeaderAlignment;
|
||||
group.stateMask = (uint)this.StateMask;
|
||||
group.state = (uint)this.State;
|
||||
|
||||
if (withId) {
|
||||
group.iGroupId = this.GroupId;
|
||||
group.mask ^= (uint)GroupMask.LVGF_GROUPID;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.Footer)) {
|
||||
group.pszFooter = this.Footer;
|
||||
group.mask ^= (uint)GroupMask.LVGF_FOOTER;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.Subtitle)) {
|
||||
group.pszSubtitle = this.Subtitle;
|
||||
group.mask ^= (uint)GroupMask.LVGF_SUBTITLE;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.Task)) {
|
||||
group.pszTask = this.Task;
|
||||
group.mask ^= (uint)GroupMask.LVGF_TASK;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.TopDescription)) {
|
||||
group.pszDescriptionTop = this.TopDescription;
|
||||
group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONTOP;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.BottomDescription)) {
|
||||
group.pszDescriptionBottom = this.BottomDescription;
|
||||
group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONBOTTOM;
|
||||
}
|
||||
|
||||
int imageIndex = this.GetImageIndex(this.TitleImage);
|
||||
if (imageIndex >= 0) {
|
||||
group.iTitleImage = imageIndex;
|
||||
group.mask ^= (uint)GroupMask.LVGF_TITLEIMAGE;
|
||||
}
|
||||
|
||||
imageIndex = this.GetImageIndex(this.ExtendedImage);
|
||||
if (imageIndex >= 0) {
|
||||
group.iExtendedImage = imageIndex;
|
||||
group.mask ^= (uint)GroupMask.LVGF_EXTENDEDIMAGE;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.SubsetTitle)) {
|
||||
group.pszSubsetTitle = this.SubsetTitle;
|
||||
group.mask ^= (uint)GroupMask.LVGF_SUBSET;
|
||||
}
|
||||
|
||||
if (this.VirtualItemCount > 0) {
|
||||
group.cItems = this.VirtualItemCount;
|
||||
group.mask ^= (uint)GroupMask.LVGF_ITEMS;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private bool GetOneState(GroupState mask) {
|
||||
if (this.Created)
|
||||
this.State = this.GetState();
|
||||
return (this.State & mask) == mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of this group from the underlying control
|
||||
/// </summary>
|
||||
protected GroupState GetState() {
|
||||
return NativeMethods.GetGroupState(this.ListView, this.GroupId, GroupState.LVGS_ALL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of this group from the underlying control
|
||||
/// </summary>
|
||||
protected int SetState(GroupState newState, GroupState mask) {
|
||||
NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2();
|
||||
group.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2)));
|
||||
group.mask = (uint)GroupMask.LVGF_STATE;
|
||||
group.state = (uint)newState;
|
||||
group.stateMask = (uint)mask;
|
||||
return NativeMethods.SetGroupInfo(this.ListView, this.GroupId, group);
|
||||
}
|
||||
|
||||
private void SetOneState(bool value, GroupState mask)
|
||||
{
|
||||
this.StateMask ^= mask;
|
||||
if (value)
|
||||
this.State ^= mask;
|
||||
else
|
||||
this.State &= ~mask;
|
||||
|
||||
if (this.Created)
|
||||
this.SetState(this.State, mask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
559
ObjectListView/Implementation/Munger.cs
Normal file
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Munger - An Interface pattern on getting and setting values from object through Reflection
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 28/11/2008 17:15
|
||||
*
|
||||
* Change log:
|
||||
* v2.5.1
|
||||
* 2012-05-01 JPP - Added IgnoreMissingAspects property
|
||||
* v2.5
|
||||
* 2011-05-20 JPP - Accessing through an indexer when the target had both a integer and
|
||||
* a string indexer didn't work reliably.
|
||||
* v2.4.1
|
||||
* 2010-08-10 JPP - Refactored into Munger/SimpleMunger. 3x faster!
|
||||
* v2.3
|
||||
* 2009-02-15 JPP - Made Munger a public class
|
||||
* 2009-01-20 JPP - Made the Munger capable of handling indexed access.
|
||||
* Incidentally, this removed the ugliness that the last change introduced.
|
||||
* 2009-01-18 JPP - Handle target objects from a DataListView (normally DataRowViews)
|
||||
* v2.0
|
||||
* 2008-11-28 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
*
|
||||
* Copyright (C) 2006-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// An instance of Munger gets a value from or puts a value into a target object. The property
|
||||
/// to be peeked (or poked) is determined from a string. The peeking or poking is done using reflection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Name of the aspect to be peeked can be a field, property or parameterless method. The name of an
|
||||
/// aspect to poke can be a field, writable property or single parameter method.
|
||||
/// <para>
|
||||
/// Aspect names can be dotted to chain a series of references.
|
||||
/// </para>
|
||||
/// <example>Order.Customer.HomeAddress.State</example>
|
||||
/// </remarks>
|
||||
public class Munger
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a do nothing Munger
|
||||
/// </summary>
|
||||
public Munger()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Munger that works on the given aspect name
|
||||
/// </summary>
|
||||
/// <param name="aspectName">The name of the </param>
|
||||
public Munger(String aspectName)
|
||||
{
|
||||
this.AspectName = aspectName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static utility methods
|
||||
|
||||
/// <summary>
|
||||
/// A helper method to put the given value into the given aspect of the given object.
|
||||
/// </summary>
|
||||
/// <remarks>This method catches and silently ignores any errors that occur
|
||||
/// while modifying the target object</remarks>
|
||||
/// <param name="target">The object to be modified</param>
|
||||
/// <param name="propertyName">The name of the property/field to be modified</param>
|
||||
/// <param name="value">The value to be assigned</param>
|
||||
/// <returns>Did the modification work?</returns>
|
||||
public static bool PutProperty(object target, string propertyName, object value) {
|
||||
try {
|
||||
Munger munger = new Munger(propertyName);
|
||||
return munger.PutValue(target, value);
|
||||
}
|
||||
catch (MungerException) {
|
||||
// Not a lot we can do about this. Something went wrong in the bowels
|
||||
// of the property. Let's take the ostrich approach and just ignore it :-)
|
||||
|
||||
// Normally, we would never just silently ignore an exception.
|
||||
// However, in this case, this is a utility method that explicitly
|
||||
// contracts to catch and ignore errors. If this is not acceptible,
|
||||
// the programmer should not use this method.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether Mungers will silently ignore missing aspect errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, if a Munger is asked to fetch a field/property/method
|
||||
/// that does not exist from a model, it returns an error message, since that
|
||||
/// condition is normally a programming error. There are some use cases where
|
||||
/// this is not an error, and the munger should simply keep quiet.
|
||||
/// </para>
|
||||
/// <para>By default this is true during release builds.</para>
|
||||
/// </remarks>
|
||||
public static bool IgnoreMissingAspects {
|
||||
get { return ignoreMissingAspects; }
|
||||
set { ignoreMissingAspects = value; }
|
||||
}
|
||||
private static bool ignoreMissingAspects
|
||||
#if !DEBUG
|
||||
= true
|
||||
#endif
|
||||
;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the aspect that is to be peeked or poked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This name can be a field, property or parameter-less method.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The name can be dotted, which chains references. If any link in the chain returns
|
||||
/// null, the entire chain is considered to return null.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>"DateOfBirth"</example>
|
||||
/// <example>"Owner.HomeAddress.Postcode"</example>
|
||||
public string AspectName
|
||||
{
|
||||
get { return aspectName; }
|
||||
set {
|
||||
aspectName = value;
|
||||
|
||||
// Clear any cache
|
||||
aspectParts = null;
|
||||
}
|
||||
}
|
||||
private string aspectName;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Extract the value indicated by our AspectName from the given target.
|
||||
/// </summary>
|
||||
/// <remarks>If the aspect name is null or empty, this will return null.</remarks>
|
||||
/// <param name="target">The object that will be peeked</param>
|
||||
/// <returns>The value read from the target</returns>
|
||||
public Object GetValue(Object target) {
|
||||
if (this.Parts.Count == 0)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return this.EvaluateParts(target, this.Parts);
|
||||
} catch (MungerException ex) {
|
||||
if (Munger.IgnoreMissingAspects)
|
||||
return null;
|
||||
|
||||
return String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'",
|
||||
ex.Munger.AspectName, ex.Target.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the value indicated by our AspectName from the given target, raising exceptions
|
||||
/// if the munger fails.
|
||||
/// </summary>
|
||||
/// <remarks>If the aspect name is null or empty, this will return null.</remarks>
|
||||
/// <param name="target">The object that will be peeked</param>
|
||||
/// <returns>The value read from the target</returns>
|
||||
public Object GetValueEx(Object target) {
|
||||
if (this.Parts.Count == 0)
|
||||
return null;
|
||||
|
||||
return this.EvaluateParts(target, this.Parts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poke the given value into the given target indicated by our AspectName.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If the AspectName is a dotted path, all the selectors bar the last
|
||||
/// are used to find the object that should be updated, and the last
|
||||
/// selector is used as the property to update on that object.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// So, if 'target' is a Person and the AspectName is "HomeAddress.Postcode",
|
||||
/// this method will first fetch "HomeAddress" property, and then try to set the
|
||||
/// "Postcode" property on the home address object.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="target">The object that will be poked</param>
|
||||
/// <param name="value">The value that will be poked into the target</param>
|
||||
/// <returns>bool indicating whether the put worked</returns>
|
||||
public bool PutValue(Object target, Object value)
|
||||
{
|
||||
if (this.Parts.Count == 0)
|
||||
return false;
|
||||
|
||||
SimpleMunger lastPart = this.Parts[this.Parts.Count - 1];
|
||||
|
||||
if (this.Parts.Count > 1) {
|
||||
List<SimpleMunger> parts = new List<SimpleMunger>(this.Parts);
|
||||
parts.RemoveAt(parts.Count - 1);
|
||||
try {
|
||||
target = this.EvaluateParts(target, parts);
|
||||
} catch (MungerException ex) {
|
||||
this.ReportPutValueException(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
try {
|
||||
return lastPart.PutValue(target, value);
|
||||
} catch (MungerException ex) {
|
||||
this.ReportPutValueException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of SimpleMungers that match our AspectName
|
||||
/// </summary>
|
||||
private IList<SimpleMunger> Parts {
|
||||
get {
|
||||
if (aspectParts == null)
|
||||
aspectParts = BuildParts(this.AspectName);
|
||||
return aspectParts;
|
||||
}
|
||||
}
|
||||
private IList<SimpleMunger> aspectParts;
|
||||
|
||||
/// <summary>
|
||||
/// Convert a possibly dotted AspectName into a list of SimpleMungers
|
||||
/// </summary>
|
||||
/// <param name="aspect"></param>
|
||||
/// <returns></returns>
|
||||
private IList<SimpleMunger> BuildParts(string aspect) {
|
||||
List<SimpleMunger> parts = new List<SimpleMunger>();
|
||||
if (!String.IsNullOrEmpty(aspect)) {
|
||||
foreach (string part in aspect.Split('.')) {
|
||||
parts.Add(new SimpleMunger(part.Trim()));
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate the given chain of SimpleMungers against an initial target.
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="parts"></param>
|
||||
/// <returns></returns>
|
||||
private object EvaluateParts(object target, IList<SimpleMunger> parts) {
|
||||
foreach (SimpleMunger part in parts) {
|
||||
if (target == null)
|
||||
break;
|
||||
target = part.GetValue(target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private void ReportPutValueException(MungerException ex) {
|
||||
//TODO: How should we report this error?
|
||||
System.Diagnostics.Debug.WriteLine("PutValue failed");
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("- Culprit aspect: {0}", ex.Munger.AspectName));
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("- Target: {0} of type {1}", ex.Target, ex.Target.GetType()));
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("- Inner exception: {0}", ex.InnerException));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A SimpleMunger deals with a single property/field/method on its target.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Munger uses a chain of these resolve a dotted aspect name.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Create a SimpleMunger
|
||||
/// </remarks>
|
||||
/// <param name="aspectName"></param>
|
||||
public class SimpleMunger(String aspectName)
|
||||
{
|
||||
|
||||
#region Life and death
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the aspect that is to be peeked or poked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This name can be a field, property or method.
|
||||
/// When using a method to get a value, the method must be parameter-less.
|
||||
/// When using a method to set a value, the method must accept 1 parameter.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It cannot be a dotted name.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string AspectName {
|
||||
get { return aspectName; }
|
||||
}
|
||||
private readonly string aspectName = aspectName;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Get a value from the given target
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public Object GetValue(Object target) {
|
||||
if (target == null)
|
||||
return null;
|
||||
|
||||
this.ResolveName(target, this.AspectName, 0);
|
||||
|
||||
try {
|
||||
if (this.resolvedPropertyInfo != null)
|
||||
return this.resolvedPropertyInfo.GetValue(target, null);
|
||||
|
||||
if (this.resolvedMethodInfo != null)
|
||||
return this.resolvedMethodInfo.Invoke(target, null);
|
||||
|
||||
if (this.resolvedFieldInfo != null)
|
||||
return this.resolvedFieldInfo.GetValue(target);
|
||||
|
||||
// If that didn't work, try to use the indexer property.
|
||||
// This covers things like dictionaries and DataRows.
|
||||
if (this.indexerPropertyInfo != null)
|
||||
return this.indexerPropertyInfo.GetValue(target, new object[] { this.AspectName });
|
||||
} catch (Exception ex) {
|
||||
// Lots of things can do wrong in these invocations
|
||||
throw new MungerException(this, target, ex);
|
||||
}
|
||||
|
||||
// If we get to here, we couldn't find a match for the aspect
|
||||
throw new MungerException(this, target, new MissingMethodException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poke the given value into the given target indicated by our AspectName.
|
||||
/// </summary>
|
||||
/// <param name="target">The object that will be poked</param>
|
||||
/// <param name="value">The value that will be poked into the target</param>
|
||||
/// <returns>bool indicating if the put worked</returns>
|
||||
public bool PutValue(object target, object value) {
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
this.ResolveName(target, this.AspectName, 1);
|
||||
|
||||
try {
|
||||
if (this.resolvedPropertyInfo != null) {
|
||||
this.resolvedPropertyInfo.SetValue(target, value, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.resolvedMethodInfo != null) {
|
||||
this.resolvedMethodInfo.Invoke(target, new object[] { value });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.resolvedFieldInfo != null) {
|
||||
this.resolvedFieldInfo.SetValue(target, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If that didn't work, try to use the indexer property.
|
||||
// This covers things like dictionaries and DataRows.
|
||||
if (this.indexerPropertyInfo != null) {
|
||||
this.indexerPropertyInfo.SetValue(target, value, new object[] { this.AspectName });
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Lots of things can do wrong in these invocations
|
||||
throw new MungerException(this, target, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
private void ResolveName(object target, string name, int numberMethodParameters) {
|
||||
|
||||
if (cachedTargetType == target.GetType() && cachedName == name && cachedNumberParameters == numberMethodParameters)
|
||||
return;
|
||||
|
||||
cachedTargetType = target.GetType();
|
||||
cachedName = name;
|
||||
cachedNumberParameters = numberMethodParameters;
|
||||
|
||||
resolvedFieldInfo = null;
|
||||
resolvedPropertyInfo = null;
|
||||
resolvedMethodInfo = null;
|
||||
indexerPropertyInfo = null;
|
||||
|
||||
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance /*| BindingFlags.NonPublic*/;
|
||||
|
||||
foreach (PropertyInfo pinfo in target.GetType().GetProperties(flags)) {
|
||||
if (pinfo.Name == name) {
|
||||
resolvedPropertyInfo = pinfo;
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we can find an string indexer property while we are here.
|
||||
// We also need to allow for old style <object> keyed collections.
|
||||
if (indexerPropertyInfo == null && pinfo.Name == "Item") {
|
||||
ParameterInfo[] par = pinfo.GetGetMethod().GetParameters();
|
||||
if (par.Length > 0) {
|
||||
Type parameterType = par[0].ParameterType;
|
||||
if (parameterType == typeof(string) || parameterType == typeof(object))
|
||||
indexerPropertyInfo = pinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FieldInfo info in target.GetType().GetFields(flags)) {
|
||||
if (info.Name == name) {
|
||||
resolvedFieldInfo = info;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MethodInfo info in target.GetType().GetMethods(flags)) {
|
||||
if (info.Name == name && info.GetParameters().Length == numberMethodParameters) {
|
||||
resolvedMethodInfo = info;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Type cachedTargetType;
|
||||
private string cachedName;
|
||||
private int cachedNumberParameters;
|
||||
|
||||
private FieldInfo resolvedFieldInfo;
|
||||
private PropertyInfo resolvedPropertyInfo;
|
||||
private MethodInfo resolvedMethodInfo;
|
||||
private PropertyInfo indexerPropertyInfo;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These exceptions are raised when a munger finds something it cannot process
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Create a MungerException
|
||||
/// </remarks>
|
||||
/// <param name="munger"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="ex"></param>
|
||||
public class MungerException(SimpleMunger munger, object target, Exception ex) : ApplicationException("Munger failed", ex)
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Get the munger that raised the exception
|
||||
/// </summary>
|
||||
public SimpleMunger Munger {
|
||||
get { return munger; }
|
||||
}
|
||||
private readonly SimpleMunger munger = munger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target that threw the exception
|
||||
/// </summary>
|
||||
public object Target {
|
||||
get { return target; }
|
||||
}
|
||||
private readonly object target = target;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't currently need this
|
||||
* 2010-08-06
|
||||
*
|
||||
|
||||
internal class SimpleBinder : Binder
|
||||
{
|
||||
public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, System.Globalization.CultureInfo culture) {
|
||||
//return Type.DefaultBinder.BindToField(
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ChangeType(object value, Type type, System.Globalization.CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void ReorderArgumentArray(ref object[] args, object state) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers) {
|
||||
if (match == null)
|
||||
throw new ArgumentNullException("match");
|
||||
|
||||
if (match.Length == 0)
|
||||
return null;
|
||||
|
||||
return match[0];
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
1226
ObjectListView/Implementation/NativeMethods.cs
Normal file
87
ObjectListView/Implementation/NullableDictionary.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* NullableDictionary - A simple Dictionary that can handle null as a key
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A simple-minded implementation of a Dictionary that can handle null as a key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the dictionary key</typeparam>
|
||||
/// <typeparam name="TValue">The type of the values to be stored</typeparam>
|
||||
/// <remarks>This is not a full implementation and is only meant to handle
|
||||
/// collecting groups by their keys, since groups can have null as a key value.</remarks>
|
||||
internal class NullableDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
|
||||
private bool hasNullKey;
|
||||
private TValue nullValue;
|
||||
|
||||
new public TValue this[TKey key] {
|
||||
get {
|
||||
if (key != null)
|
||||
return base[key];
|
||||
|
||||
if (this.hasNullKey)
|
||||
return this.nullValue;
|
||||
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
set {
|
||||
if (key == null) {
|
||||
this.hasNullKey = true;
|
||||
this.nullValue = value;
|
||||
} else
|
||||
base[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
new public bool ContainsKey(TKey key) {
|
||||
return key == null ? this.hasNullKey : base.ContainsKey(key);
|
||||
}
|
||||
|
||||
new public IList Keys {
|
||||
get {
|
||||
ArrayList list = new ArrayList(base.Keys);
|
||||
if (this.hasNullKey)
|
||||
list.Add(null);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
new public IList<TValue> Values {
|
||||
get {
|
||||
List<TValue> list = new List<TValue>(base.Values);
|
||||
if (this.hasNullKey)
|
||||
list.Add(this.nullValue);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
321
ObjectListView/Implementation/OLVListItem.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* OLVListItem - A row in an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2015-08-22 JPP - Added OLVListItem.SelectedBackColor and SelectedForeColor
|
||||
* 2015-06-09 JPP - Added HasAnyHyperlinks property
|
||||
* v2.8
|
||||
* 2014-09-27 JPP - Remove faulty caching of CheckState
|
||||
* 2014-05-06 JPP - Added OLVListItem.Enabled flag
|
||||
* vOld
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2015 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// OLVListItems are specialized ListViewItems that know which row object they came from,
|
||||
/// and the row index at which they are displayed, even when in group view mode. They
|
||||
/// also know the image they should draw against themselves
|
||||
/// </summary>
|
||||
public class OLVListItem : ListViewItem {
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListItem for the given row object
|
||||
/// </summary>
|
||||
public OLVListItem(object rowObject) {
|
||||
this.rowObject = rowObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListItem for the given row object, represented by the given string and image
|
||||
/// </summary>
|
||||
public OLVListItem(object rowObject, string text, Object image)
|
||||
: base(text, -1) {
|
||||
this.rowObject = rowObject;
|
||||
this.imageSelector = image;
|
||||
}
|
||||
|
||||
#endregion.
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding rectangle of the item, including all subitems
|
||||
/// </summary>
|
||||
new public Rectangle Bounds {
|
||||
get {
|
||||
try {
|
||||
return base.Bounds;
|
||||
}
|
||||
catch (System.ArgumentException) {
|
||||
// If the item is part of a collapsed group, Bounds will throw an exception
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how many pixels will be left blank around each cell of this item
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public Rectangle? CellPadding {
|
||||
get { return this.cellPadding; }
|
||||
set { this.cellPadding = value; }
|
||||
}
|
||||
private Rectangle? cellPadding;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the cells of this item will be vertically aligned
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public StringAlignment? CellVerticalAlignment {
|
||||
get { return this.cellVerticalAlignment; }
|
||||
set { this.cellVerticalAlignment = value; }
|
||||
}
|
||||
private StringAlignment? cellVerticalAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the checkedness of this item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Virtual lists don't handle checkboxes well, so we have to intercept attempts to change them
|
||||
/// through the items, and change them into something that will work.
|
||||
/// Unfortunately, this won't work if this property is set through the base class, since
|
||||
/// the property is not declared as virtual.
|
||||
/// </remarks>
|
||||
new public bool Checked {
|
||||
get {
|
||||
return base.Checked;
|
||||
}
|
||||
set {
|
||||
if (this.Checked != value) {
|
||||
if (value)
|
||||
((ObjectListView)this.ListView).CheckObject(this.RowObject);
|
||||
else
|
||||
((ObjectListView)this.ListView).UncheckObject(this.RowObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable tri-state checkbox.
|
||||
/// </summary>
|
||||
/// <remarks>.NET's Checked property was not built to handle tri-state checkboxes,
|
||||
/// and will return True for both Checked and Indeterminate states.</remarks>
|
||||
public CheckState CheckState {
|
||||
get {
|
||||
switch (this.StateImageIndex) {
|
||||
case 0:
|
||||
return System.Windows.Forms.CheckState.Unchecked;
|
||||
case 1:
|
||||
return System.Windows.Forms.CheckState.Checked;
|
||||
case 2:
|
||||
return System.Windows.Forms.CheckState.Indeterminate;
|
||||
default:
|
||||
return System.Windows.Forms.CheckState.Unchecked;
|
||||
}
|
||||
}
|
||||
set {
|
||||
switch (value) {
|
||||
case System.Windows.Forms.CheckState.Unchecked:
|
||||
this.StateImageIndex = 0;
|
||||
break;
|
||||
case System.Windows.Forms.CheckState.Checked:
|
||||
this.StateImageIndex = 1;
|
||||
break;
|
||||
case System.Windows.Forms.CheckState.Indeterminate:
|
||||
this.StateImageIndex = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this item has any decorations set for it.
|
||||
/// </summary>
|
||||
public bool HasDecoration {
|
||||
get {
|
||||
return this.decorations != null && this.decorations.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the decoration that will be drawn over this item
|
||||
/// </summary>
|
||||
/// <remarks>Setting this replaces all other decorations</remarks>
|
||||
public IDecoration Decoration {
|
||||
get {
|
||||
if (this.HasDecoration)
|
||||
return this.Decorations[0];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
set {
|
||||
this.Decorations.Clear();
|
||||
if (value != null)
|
||||
this.Decorations.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of decorations that will be drawn over this item
|
||||
/// </summary>
|
||||
public IList<IDecoration> Decorations {
|
||||
get {
|
||||
if (this.decorations == null)
|
||||
this.decorations = new List<IDecoration>();
|
||||
return this.decorations;
|
||||
}
|
||||
}
|
||||
private IList<IDecoration> decorations;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this row can be selected and activated
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get { return this.enabled; }
|
||||
internal set { this.enabled = value; }
|
||||
}
|
||||
private bool enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether any cell on this item is showing a hyperlink
|
||||
/// </summary>
|
||||
public bool HasAnyHyperlinks {
|
||||
get {
|
||||
foreach (OLVListSubItem subItem in this.SubItems) {
|
||||
if (!String.IsNullOrEmpty(subItem.Url))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the image that should be shown against this item
|
||||
/// </summary>
|
||||
/// <remarks><para>This can be an Image, a string or an int. A string or an int will
|
||||
/// be used as an index into the small image list.</para></remarks>
|
||||
public Object ImageSelector {
|
||||
get { return imageSelector; }
|
||||
set {
|
||||
imageSelector = value;
|
||||
if (value is Int32)
|
||||
this.ImageIndex = (Int32)value;
|
||||
else if (value is String)
|
||||
this.ImageKey = (String)value;
|
||||
else
|
||||
this.ImageIndex = -1;
|
||||
}
|
||||
}
|
||||
private Object imageSelector;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the the model object that is source of the data for this list item.
|
||||
/// </summary>
|
||||
public object RowObject {
|
||||
get { return rowObject; }
|
||||
set { rowObject = value; }
|
||||
}
|
||||
private object rowObject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color that will be used for this row's background when it is selected and
|
||||
/// the control is focused.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>To work reliably, this property must be set during a FormatRow event.</para>
|
||||
/// <para>
|
||||
/// If this is not set, the normal selection BackColor will be used.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Color? SelectedBackColor {
|
||||
get { return this.selectedBackColor; }
|
||||
set { this.selectedBackColor = value; }
|
||||
}
|
||||
private Color? selectedBackColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color that will be used for this row's foreground when it is selected and
|
||||
/// the control is focused.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>To work reliably, this property must be set during a FormatRow event.</para>
|
||||
/// <para>
|
||||
/// If this is not set, the normal selection ForeColor will be used.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Color? SelectedForeColor
|
||||
{
|
||||
get { return this.selectedForeColor; }
|
||||
set { this.selectedForeColor = value; }
|
||||
}
|
||||
private Color? selectedForeColor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Accessing
|
||||
|
||||
/// <summary>
|
||||
/// Return the sub item at the given index
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the subitem to be returned</param>
|
||||
/// <returns>An OLVListSubItem</returns>
|
||||
public virtual OLVListSubItem GetSubItem(int index) {
|
||||
if (index >= 0 && index < this.SubItems.Count)
|
||||
return (OLVListSubItem)this.SubItems[index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Return bounds of the given subitem
|
||||
/// </summary>
|
||||
/// <remarks>This correctly calculates the bounds even for column 0.</remarks>
|
||||
public virtual Rectangle GetSubItemBounds(int subItemIndex) {
|
||||
if (subItemIndex == 0) {
|
||||
Rectangle r = this.Bounds;
|
||||
Point sides = NativeMethods.GetScrolledColumnSides(this.ListView, subItemIndex);
|
||||
r.X = sides.X + 1;
|
||||
r.Width = sides.Y - sides.X;
|
||||
return r;
|
||||
}
|
||||
|
||||
OLVListSubItem subItem = this.GetSubItem(subItemIndex);
|
||||
return subItem == null ? new Rectangle() : subItem.Bounds;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
173
ObjectListView/Implementation/OLVListSubItem.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* OLVListSubItem - A single cell in an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A ListViewSubItem that knows which image should be drawn against it.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public class OLVListSubItem : ListViewItem.ListViewSubItem {
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListSubItem
|
||||
/// </summary>
|
||||
public OLVListSubItem() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListSubItem that shows the given string and image
|
||||
/// </summary>
|
||||
public OLVListSubItem(object modelValue, string text, Object image) {
|
||||
this.ModelValue = modelValue;
|
||||
this.Text = text;
|
||||
this.ImageSelector = image;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how many pixels will be left blank around this cell
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public Rectangle? CellPadding {
|
||||
get { return this.cellPadding; }
|
||||
set { this.cellPadding = value; }
|
||||
}
|
||||
private Rectangle? cellPadding;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how this cell will be vertically aligned
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public StringAlignment? CellVerticalAlignment {
|
||||
get { return this.cellVerticalAlignment; }
|
||||
set { this.cellVerticalAlignment = value; }
|
||||
}
|
||||
private StringAlignment? cellVerticalAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model value is being displayed by this subitem.
|
||||
/// </summary>
|
||||
public object ModelValue
|
||||
{
|
||||
get { return modelValue; }
|
||||
private set { modelValue = value; }
|
||||
}
|
||||
private object modelValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this subitem has any decorations set for it.
|
||||
/// </summary>
|
||||
public bool HasDecoration {
|
||||
get {
|
||||
return this.decorations != null && this.decorations.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the decoration that will be drawn over this item
|
||||
/// </summary>
|
||||
/// <remarks>Setting this replaces all other decorations</remarks>
|
||||
public IDecoration Decoration {
|
||||
get {
|
||||
return this.HasDecoration ? this.Decorations[0] : null;
|
||||
}
|
||||
set {
|
||||
this.Decorations.Clear();
|
||||
if (value != null)
|
||||
this.Decorations.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of decorations that will be drawn over this item
|
||||
/// </summary>
|
||||
public IList<IDecoration> Decorations {
|
||||
get {
|
||||
if (this.decorations == null)
|
||||
this.decorations = new List<IDecoration>();
|
||||
return this.decorations;
|
||||
}
|
||||
}
|
||||
private IList<IDecoration> decorations;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the image that should be shown against this item
|
||||
/// </summary>
|
||||
/// <remarks><para>This can be an Image, a string or an int. A string or an int will
|
||||
/// be used as an index into the small image list.</para></remarks>
|
||||
public Object ImageSelector {
|
||||
get { return imageSelector; }
|
||||
set { imageSelector = value; }
|
||||
}
|
||||
private Object imageSelector;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the url that should be invoked when this subitem is clicked
|
||||
/// </summary>
|
||||
public string Url
|
||||
{
|
||||
get { return this.url; }
|
||||
set { this.url = value; }
|
||||
}
|
||||
private string url;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this cell is selected
|
||||
/// </summary>
|
||||
public bool Selected
|
||||
{
|
||||
get { return this.selected; }
|
||||
set { this.selected = value; }
|
||||
}
|
||||
private bool selected;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation Properties
|
||||
|
||||
/// <summary>
|
||||
/// Return the state of the animatation of the image on this subitem.
|
||||
/// Null means there is either no image, or it is not an animation
|
||||
/// </summary>
|
||||
internal ImageRenderer.AnimationState AnimationState;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
388
ObjectListView/Implementation/OlvListViewHitTestInfo.cs
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* OlvListViewHitTestInfo - All information gathered during a OlvHitTest() operation
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// An indication of where a hit was within ObjectListView cell
|
||||
/// </summary>
|
||||
public enum HitTestLocation {
|
||||
/// <summary>
|
||||
/// Nowhere
|
||||
/// </summary>
|
||||
Nothing,
|
||||
|
||||
/// <summary>
|
||||
/// On the text
|
||||
/// </summary>
|
||||
Text,
|
||||
|
||||
/// <summary>
|
||||
/// On the image
|
||||
/// </summary>
|
||||
Image,
|
||||
|
||||
/// <summary>
|
||||
/// On the checkbox
|
||||
/// </summary>
|
||||
CheckBox,
|
||||
|
||||
/// <summary>
|
||||
/// On the expand button (TreeListView)
|
||||
/// </summary>
|
||||
ExpandButton,
|
||||
|
||||
/// <summary>
|
||||
/// in a button (cell must have ButtonRenderer)
|
||||
/// </summary>
|
||||
Button,
|
||||
|
||||
/// <summary>
|
||||
/// in the cell but not in any more specific location
|
||||
/// </summary>
|
||||
InCell,
|
||||
|
||||
/// <summary>
|
||||
/// UserDefined location1 (used for custom renderers)
|
||||
/// </summary>
|
||||
UserDefined,
|
||||
|
||||
/// <summary>
|
||||
/// On the expand/collapse widget of the group
|
||||
/// </summary>
|
||||
GroupExpander,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere on a group
|
||||
/// </summary>
|
||||
Group,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere in a column header
|
||||
/// </summary>
|
||||
Header,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere in a column header checkbox
|
||||
/// </summary>
|
||||
HeaderCheckBox,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere in a header divider
|
||||
/// </summary>
|
||||
HeaderDivider,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection of ListViewHitTest constants
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum HitTestLocationEx {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_NOWHERE = 0x00000001,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEMICON = 0x00000002,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEMLABEL = 0x00000004,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEMSTATEICON = 0x00000008,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON),
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ABOVE = 0x00000008,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_BELOW = 0x00000010,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_TORIGHT = 0x00000020,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_TOLEFT = 0x00000040,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_HEADER = 0x10000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_FOOTER = 0x20000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_COLLAPSE = 0x40000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_BACKGROUND = -2147483648, // 0x80000000
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_STATEICON = 0x01000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_SUBSETLINK = 0x02000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP = (LVHT_EX_GROUP_BACKGROUND | LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_FOOTER | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK),
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD = (LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK),
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_ONCONTENTS = 0x04000000, // On item AND not on the background
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_FOOTER = 0x08000000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class encapsulate the information gathered during a OlvHitTest()
|
||||
/// operation.
|
||||
/// </summary>
|
||||
/// <remarks>Custom renderers can use HitTestLocation.UserDefined and the UserData
|
||||
/// object to store more specific locations for use during event handlers.</remarks>
|
||||
public class OlvListViewHitTestInfo {
|
||||
|
||||
/// <summary>
|
||||
/// Create a OlvListViewHitTestInfo
|
||||
/// </summary>
|
||||
public OlvListViewHitTestInfo(OLVListItem olvListItem, OLVListSubItem subItem, int flags, OLVGroup group, int iColumn)
|
||||
{
|
||||
this.item = olvListItem;
|
||||
this.subItem = subItem;
|
||||
this.location = ConvertNativeFlagsToDotNetLocation(olvListItem, flags);
|
||||
this.HitTestLocationEx = (HitTestLocationEx)flags;
|
||||
this.Group = group;
|
||||
this.ColumnIndex = iColumn;
|
||||
this.ListView = olvListItem == null ? null : (ObjectListView)olvListItem.ListView;
|
||||
|
||||
switch (location) {
|
||||
case ListViewHitTestLocations.StateImage:
|
||||
this.HitTestLocation = HitTestLocation.CheckBox;
|
||||
break;
|
||||
case ListViewHitTestLocations.Image:
|
||||
this.HitTestLocation = HitTestLocation.Image;
|
||||
break;
|
||||
case ListViewHitTestLocations.Label:
|
||||
this.HitTestLocation = HitTestLocation.Text;
|
||||
break;
|
||||
default:
|
||||
if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE) == HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE)
|
||||
this.HitTestLocation = HitTestLocation.GroupExpander;
|
||||
else if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD) != 0)
|
||||
this.HitTestLocation = HitTestLocation.Group;
|
||||
else
|
||||
this.HitTestLocation = HitTestLocation.Nothing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a OlvListViewHitTestInfo when the header was hit
|
||||
/// </summary>
|
||||
public OlvListViewHitTestInfo(ObjectListView olv, int iColumn, bool isOverCheckBox, int iDivider) {
|
||||
this.ListView = olv;
|
||||
this.ColumnIndex = iColumn;
|
||||
this.HeaderDividerIndex = iDivider;
|
||||
this.HitTestLocation = isOverCheckBox ? HitTestLocation.HeaderCheckBox : (iDivider < 0 ? HitTestLocation.Header : HitTestLocation.HeaderDivider);
|
||||
}
|
||||
|
||||
private static ListViewHitTestLocations ConvertNativeFlagsToDotNetLocation(OLVListItem hitItem, int flags)
|
||||
{
|
||||
// Untangle base .NET behaviour.
|
||||
|
||||
// In Windows SDK, the value 8 can have two meanings here: LVHT_ONITEMSTATEICON or LVHT_ABOVE.
|
||||
// .NET changes these to be:
|
||||
// - LVHT_ABOVE becomes ListViewHitTestLocations.AboveClientArea (which is 0x100).
|
||||
// - LVHT_ONITEMSTATEICON becomes ListViewHitTestLocations.StateImage (which is 0x200).
|
||||
// So, if we see the 8 bit set in flags, we change that to either a state image hit
|
||||
// (if we hit an item) or to AboveClientAream if nothing was hit.
|
||||
|
||||
if ((8 & flags) == 8)
|
||||
return (ListViewHitTestLocations)(0xf7 & flags | (hitItem == null ? 0x100 : 0x200));
|
||||
|
||||
// Mask off the LVHT_EX_XXXX values since ListViewHitTestLocations doesn't have them
|
||||
return (ListViewHitTestLocations)(flags & 0xffff);
|
||||
}
|
||||
|
||||
#region Public fields
|
||||
|
||||
/// <summary>
|
||||
/// Where is the hit location?
|
||||
/// </summary>
|
||||
public HitTestLocation HitTestLocation;
|
||||
|
||||
/// <summary>
|
||||
/// Where is the hit location?
|
||||
/// </summary>
|
||||
public HitTestLocationEx HitTestLocationEx;
|
||||
|
||||
/// <summary>
|
||||
/// Which group was hit?
|
||||
/// </summary>
|
||||
public OLVGroup Group;
|
||||
|
||||
/// <summary>
|
||||
/// Custom renderers can use this information to supply more details about the hit location
|
||||
/// </summary>
|
||||
public Object UserData;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public read-only properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item that was hit
|
||||
/// </summary>
|
||||
public OLVListItem Item {
|
||||
get { return item; }
|
||||
internal set { item = value; }
|
||||
}
|
||||
private OLVListItem item;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subitem that was hit
|
||||
/// </summary>
|
||||
public OLVListSubItem SubItem {
|
||||
get { return subItem; }
|
||||
internal set { subItem = value; }
|
||||
}
|
||||
private OLVListSubItem subItem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the part of the subitem that was hit
|
||||
/// </summary>
|
||||
public ListViewHitTestLocations Location {
|
||||
get { return location; }
|
||||
internal set { location = value; }
|
||||
}
|
||||
private ListViewHitTestLocations location;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that was tested
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return listView; }
|
||||
internal set { listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model object that was hit
|
||||
/// </summary>
|
||||
public Object RowObject {
|
||||
get {
|
||||
return this.Item == null ? null : this.Item.RowObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the row under the hit point or -1
|
||||
/// </summary>
|
||||
public int RowIndex {
|
||||
get { return this.Item == null ? -1 : this.Item.Index; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the column under the hit point
|
||||
/// </summary>
|
||||
public int ColumnIndex {
|
||||
get { return columnIndex; }
|
||||
internal set { columnIndex = value; }
|
||||
}
|
||||
private int columnIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the header divider
|
||||
/// </summary>
|
||||
public int HeaderDividerIndex {
|
||||
get { return headerDividerIndex; }
|
||||
internal set { headerDividerIndex = value; }
|
||||
}
|
||||
private int headerDividerIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the column that was hit
|
||||
/// </summary>
|
||||
public OLVColumn Column {
|
||||
get {
|
||||
int index = this.ColumnIndex;
|
||||
return index < 0 || this.ListView == null ? null : this.ListView.GetColumn(index);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("HitTestLocation: {0}, HitTestLocationEx: {1}, Item: {2}, SubItem: {3}, Location: {4}, Group: {5}, ColumnIndex: {6}",
|
||||
this.HitTestLocation, this.HitTestLocationEx, this.item, this.subItem, this.location, this.Group, this.ColumnIndex);
|
||||
}
|
||||
|
||||
internal class HeaderHitTestInfo
|
||||
{
|
||||
public int ColumnIndex;
|
||||
public bool IsOverCheckBox;
|
||||
public int OverDividerIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
262
ObjectListView/Implementation/TreeDataSourceAdapter.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A TreeDataSourceAdapter knows how to build a tree structure from a binding list.
|
||||
/// </summary>
|
||||
/// <remarks>To build a tree</remarks>
|
||||
public class TreeDataSourceAdapter : DataSourceAdapter
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a data source adaptor that knows how to build a tree structure
|
||||
/// </summary>
|
||||
/// <param name="tlv"></param>
|
||||
public TreeDataSourceAdapter(DataTreeListView tlv)
|
||||
: base(tlv) {
|
||||
this.treeListView = tlv;
|
||||
this.treeListView.CanExpandGetter = delegate(object model) { return this.CalculateHasChildren(model); };
|
||||
this.treeListView.ChildrenGetter = delegate(object model) { return this.CalculateChildren(model); };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property/column that uniquely identifies each row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value contained by this column must be unique across all rows
|
||||
/// in the data source. Odd and unpredictable things will happen if two
|
||||
/// rows have the same id.
|
||||
/// </para>
|
||||
/// <para>Null cannot be a valid key value.</para>
|
||||
/// </remarks>
|
||||
public virtual string KeyAspectName {
|
||||
get { return keyAspectName; }
|
||||
set {
|
||||
if (keyAspectName == value)
|
||||
return;
|
||||
keyAspectName = value;
|
||||
this.keyMunger = new Munger(this.KeyAspectName);
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
private string keyAspectName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property/column that contains the key of
|
||||
/// the parent of a row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The test condition for deciding if one row is the parent of another is functionally
|
||||
/// equivilent to this:
|
||||
/// <code>
|
||||
/// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName])
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>Unlike key value, parent keys can be null but a null parent key can only be used
|
||||
/// to identify root objects.</para>
|
||||
/// </remarks>
|
||||
public virtual string ParentKeyAspectName {
|
||||
get { return parentKeyAspectName; }
|
||||
set {
|
||||
if (parentKeyAspectName == value)
|
||||
return;
|
||||
parentKeyAspectName = value;
|
||||
this.parentKeyMunger = new Munger(this.ParentKeyAspectName);
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
private string parentKeyAspectName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value that identifies a row as a root object.
|
||||
/// When the ParentKey of a row equals the RootKeyValue, that row will
|
||||
/// be treated as root of the TreeListView.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The test condition for deciding a root object is functionally
|
||||
/// equivilent to this:
|
||||
/// <code>
|
||||
/// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue)
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>The RootKeyValue can be null.</para>
|
||||
/// </remarks>
|
||||
public virtual object RootKeyValue {
|
||||
get { return rootKeyValue; }
|
||||
set {
|
||||
if (Equals(rootKeyValue, value))
|
||||
return;
|
||||
rootKeyValue = value;
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
private object rootKeyValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not the key columns (id and parent id) should
|
||||
/// be shown to the user.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect
|
||||
/// afterwards.</remarks>
|
||||
public virtual bool ShowKeyColumns {
|
||||
get { return showKeyColumns; }
|
||||
set { showKeyColumns = value; }
|
||||
}
|
||||
private bool showKeyColumns = true;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DataTreeListView that is being managed
|
||||
/// </summary>
|
||||
protected DataTreeListView TreeListView {
|
||||
get { return treeListView; }
|
||||
}
|
||||
private readonly DataTreeListView treeListView;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override void InitializeDataSource() {
|
||||
base.InitializeDataSource();
|
||||
this.TreeListView.RebuildAll(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override void SetListContents() {
|
||||
this.TreeListView.Roots = this.CalculateRoots();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ShouldCreateColumn(PropertyDescriptor property) {
|
||||
// If the property is a key column, and we aren't supposed to show keys, don't show it
|
||||
if (!this.ShowKeyColumns && (property.Name == this.KeyAspectName || property.Name == this.ParentKeyAspectName))
|
||||
return false;
|
||||
|
||||
return base.ShouldCreateColumn(property);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void HandleListChangedItemChanged(System.ComponentModel.ListChangedEventArgs e) {
|
||||
// If the id or the parent id of a row changes, we just rebuild everything.
|
||||
// We can't do anything more specific. We don't know what the previous values, so we can't
|
||||
// tell the previous parent to refresh itself. If the id itself has changed, things that used
|
||||
// to be children will no longer be children. Just rebuild everything.
|
||||
// It seems PropertyDescriptor is only filled in .NET 4 :(
|
||||
if (e.PropertyDescriptor != null &&
|
||||
(e.PropertyDescriptor.Name == this.KeyAspectName ||
|
||||
e.PropertyDescriptor.Name == this.ParentKeyAspectName))
|
||||
this.InitializeDataSource();
|
||||
else
|
||||
base.HandleListChangedItemChanged(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
protected override void ChangePosition(int index) {
|
||||
// We can't use our base method directly, since the normal position management
|
||||
// doesn't know about our tree structure. They treat our dataset as a flat list
|
||||
// but we have a collapsable structure. This means that the 5'th row to them
|
||||
// may not even be visible to us
|
||||
|
||||
// To display the n'th row, we have to make sure that all its ancestors
|
||||
// are expanded. Then we will be able to select it.
|
||||
object model = this.CurrencyManager.List[index];
|
||||
object parent = this.CalculateParent(model);
|
||||
while (parent != null && !this.TreeListView.IsExpanded(parent)) {
|
||||
this.TreeListView.Expand(parent);
|
||||
parent = this.CalculateParent(parent);
|
||||
}
|
||||
|
||||
base.ChangePosition(index);
|
||||
}
|
||||
|
||||
private IEnumerable CalculateRoots() {
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object parentKey = this.GetParentValue(x);
|
||||
if (Object.Equals(this.RootKeyValue, parentKey))
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CalculateHasChildren(object model) {
|
||||
object keyValue = this.GetKeyValue(model);
|
||||
if (keyValue == null)
|
||||
return false;
|
||||
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object parentKey = this.GetParentValue(x);
|
||||
if (Object.Equals(keyValue, parentKey))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable CalculateChildren(object model) {
|
||||
object keyValue = this.GetKeyValue(model);
|
||||
if (keyValue != null) {
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object parentKey = this.GetParentValue(x);
|
||||
if (Object.Equals(keyValue, parentKey))
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object CalculateParent(object model) {
|
||||
object parentValue = this.GetParentValue(model);
|
||||
if (parentValue == null)
|
||||
return null;
|
||||
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object key = this.GetKeyValue(x);
|
||||
if (Object.Equals(parentValue, key))
|
||||
return x;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private object GetKeyValue(object model) {
|
||||
return this.keyMunger == null ? null : this.keyMunger.GetValue(model);
|
||||
}
|
||||
|
||||
private object GetParentValue(object model) {
|
||||
return this.parentKeyMunger == null ? null : this.parentKeyMunger.GetValue(model);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Munger keyMunger;
|
||||
private Munger parentKeyMunger;
|
||||
}
|
||||
}
|
||||
352
ObjectListView/Implementation/VirtualGroups.cs
Normal file
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* Virtual groups - Classes and interfaces needed to implement virtual groups
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 28/08/2009 11:10am
|
||||
*
|
||||
* Change log:
|
||||
* 2011-02-21 JPP - Correctly honor group comparer and collapsible groups settings
|
||||
* v2.3
|
||||
* 2009-08-28 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A IVirtualGroups is the interface that a virtual list must implement to support virtual groups
|
||||
/// </summary>
|
||||
public interface IVirtualGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the list of groups that should be shown according to the given parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
IList<OLVGroup> GetGroups(GroupingParameters parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the item that appears at the given position within the given group.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="indexWithinGroup"></param>
|
||||
/// <returns></returns>
|
||||
int GetGroupMember(OLVGroup group, int indexWithinGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the group to which the given item belongs
|
||||
/// </summary>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
int GetGroup(int itemIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Return the index at which the given item is shown in the given group
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
int GetIndexWithinGroup(OLVGroup group, int itemIndex);
|
||||
|
||||
/// <summary>
|
||||
/// A hint that the given range of items are going to be required
|
||||
/// </summary>
|
||||
/// <param name="fromGroupIndex"></param>
|
||||
/// <param name="fromIndex"></param>
|
||||
/// <param name="toGroupIndex"></param>
|
||||
/// <param name="toIndex"></param>
|
||||
void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a safe, do nothing implementation of a grouping strategy
|
||||
/// </summary>
|
||||
public class AbstractVirtualGroups : IVirtualGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the list of groups that should be shown according to the given parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IList<OLVGroup> GetGroups(GroupingParameters parameters) {
|
||||
return new List<OLVGroup>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the item that appears at the given position within the given group.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="indexWithinGroup"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetGroupMember(OLVGroup group, int indexWithinGroup) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the group to which the given item belongs
|
||||
/// </summary>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetGroup(int itemIndex) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index at which the given item is shown in the given group
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetIndexWithinGroup(OLVGroup group, int itemIndex) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A hint that the given range of items are going to be required
|
||||
/// </summary>
|
||||
/// <param name="fromGroupIndex"></param>
|
||||
/// <param name="fromIndex"></param>
|
||||
/// <param name="toGroupIndex"></param>
|
||||
/// <param name="toIndex"></param>
|
||||
public virtual void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Provides grouping functionality to a FastObjectListView
|
||||
/// </summary>
|
||||
public class FastListGroupingStrategy : AbstractVirtualGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Create groups for FastListView
|
||||
/// </summary>
|
||||
/// <param name="parmameters"></param>
|
||||
/// <returns></returns>
|
||||
public override IList<OLVGroup> GetGroups(GroupingParameters parmameters) {
|
||||
|
||||
// There is a lot of overlap between this method and ObjectListView.MakeGroups()
|
||||
// Any changes made here may need to be reflected there
|
||||
|
||||
// This strategy can only be used on FastObjectListViews
|
||||
FastObjectListView folv = (FastObjectListView)parmameters.ListView;
|
||||
|
||||
// Separate the list view items into groups, using the group key as the descrimanent
|
||||
int objectCount = 0;
|
||||
NullableDictionary<object, List<object>> map = new NullableDictionary<object, List<object>>();
|
||||
foreach (object model in folv.FilteredObjects) {
|
||||
object key = parmameters.GroupByColumn.GetGroupKey(model);
|
||||
if (!map.ContainsKey(key))
|
||||
map[key] = new List<object>();
|
||||
map[key].Add(model);
|
||||
objectCount++;
|
||||
}
|
||||
|
||||
// Sort the items within each group
|
||||
// TODO: Give parameters a ModelComparer property
|
||||
OLVColumn primarySortColumn = parmameters.SortItemsByPrimaryColumn ? parmameters.ListView.GetColumn(0) : parmameters.PrimarySort;
|
||||
ModelObjectComparer sorter = new ModelObjectComparer(primarySortColumn, parmameters.PrimarySortOrder,
|
||||
parmameters.SecondarySort, parmameters.SecondarySortOrder);
|
||||
foreach (object key in map.Keys) {
|
||||
map[key].Sort(sorter);
|
||||
}
|
||||
|
||||
// Make a list of the required groups
|
||||
List<OLVGroup> groups = new List<OLVGroup>();
|
||||
foreach (object key in map.Keys) {
|
||||
string title = parmameters.GroupByColumn.ConvertGroupKeyToTitle(key);
|
||||
if (!String.IsNullOrEmpty(parmameters.TitleFormat)) {
|
||||
int count = map[key].Count;
|
||||
string format = (count == 1 ? parmameters.TitleSingularFormat : parmameters.TitleFormat);
|
||||
try {
|
||||
title = String.Format(format, title, count);
|
||||
} catch (FormatException) {
|
||||
title = "Invalid group format: " + format;
|
||||
}
|
||||
}
|
||||
OLVGroup lvg = new OLVGroup(title);
|
||||
lvg.Collapsible = folv.HasCollapsibleGroups;
|
||||
lvg.Key = key;
|
||||
lvg.SortValue = key as IComparable;
|
||||
lvg.Contents = map[key].ConvertAll<int>(delegate(object x) { return folv.IndexOf(x); });
|
||||
lvg.VirtualItemCount = map[key].Count;
|
||||
if (parmameters.GroupByColumn.GroupFormatter != null)
|
||||
parmameters.GroupByColumn.GroupFormatter(lvg, parmameters);
|
||||
groups.Add(lvg);
|
||||
}
|
||||
|
||||
// Sort the groups
|
||||
if (parmameters.GroupByOrder != SortOrder.None)
|
||||
groups.Sort(parmameters.GroupComparer ?? new OLVGroupComparer(parmameters.GroupByOrder));
|
||||
|
||||
// Build an array that remembers which group each item belongs to.
|
||||
this.indexToGroupMap = new List<int>(objectCount);
|
||||
this.indexToGroupMap.AddRange(new int[objectCount]);
|
||||
|
||||
for (int i = 0; i < groups.Count; i++) {
|
||||
OLVGroup group = groups[i];
|
||||
List<int> members = (List<int>)group.Contents;
|
||||
foreach (int j in members)
|
||||
this.indexToGroupMap[j] = i;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
private List<int> indexToGroupMap;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="indexWithinGroup"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetGroupMember(OLVGroup group, int indexWithinGroup) {
|
||||
return (int)group.Contents[indexWithinGroup];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetGroup(int itemIndex) {
|
||||
return this.indexToGroupMap[itemIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetIndexWithinGroup(OLVGroup group, int itemIndex) {
|
||||
return group.Contents.IndexOf(itemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is the COM interface that a ListView must be given in order for groups in virtual lists to work.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This interface is NOT documented by MS. It was found on Greg Chapell's site. This means that there is
|
||||
/// no guarantee that it will work on future versions of Windows, nor continue to work on current ones.
|
||||
/// </remarks>
|
||||
[ComImport(),
|
||||
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
|
||||
Guid("44C09D56-8D3B-419D-A462-7B956B105B47")]
|
||||
internal interface IOwnerDataCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// Not sure what this does
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <param name="pt"></param>
|
||||
void GetItemPosition(int i, out NativeMethods.POINT pt);
|
||||
|
||||
/// <summary>
|
||||
/// Not sure what this does
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="pt"></param>
|
||||
void SetItemPosition(int t, NativeMethods.POINT pt);
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the item that occurs at the n'th position of the indicated group.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group</param>
|
||||
/// <param name="n">Index within the group</param>
|
||||
/// <param name="itemIndex">Index of the item within the whole list</param>
|
||||
void GetItemInGroup(int groupIndex, int n, out int itemIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the group to which the given item belongs
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">Index of the item within the whole list</param>
|
||||
/// <param name="occurrenceCount">Which occurences of the item is wanted</param>
|
||||
/// <param name="groupIndex">Index of the group</param>
|
||||
void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of groups that contain the given item
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">Index of the item within the whole list</param>
|
||||
/// <param name="occurrenceCount">How many groups does it occur within</param>
|
||||
void GetItemGroupCount(int itemIndex, out int occurrenceCount);
|
||||
|
||||
/// <summary>
|
||||
/// A hint to prepare any cache for the given range of requests
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <param name="j"></param>
|
||||
void OnCacheHint(NativeMethods.LVITEMINDEX i, NativeMethods.LVITEMINDEX j);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default implementation of the IOwnerDataCallback interface
|
||||
/// </summary>
|
||||
[Guid("6FC61F50-80E8-49b4-B200-3F38D3865ABD")]
|
||||
internal class OwnerDataCallbackImpl(VirtualObjectListView olv) : IOwnerDataCallback
|
||||
{
|
||||
VirtualObjectListView olv = olv;
|
||||
|
||||
#region IOwnerDataCallback Members
|
||||
|
||||
public void GetItemPosition(int i, out NativeMethods.POINT pt) {
|
||||
//System.Diagnostics.Debug.WriteLine("GetItemPosition");
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetItemPosition(int t, NativeMethods.POINT pt) {
|
||||
//System.Diagnostics.Debug.WriteLine("SetItemPosition");
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void GetItemInGroup(int groupIndex, int n, out int itemIndex) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("-> GetItemInGroup({0}, {1})", groupIndex, n));
|
||||
itemIndex = this.olv.GroupingStrategy.GetGroupMember(this.olv.OLVGroups[groupIndex], n);
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", itemIndex));
|
||||
}
|
||||
|
||||
public void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroup({0}, {1})", itemIndex, occurrenceCount));
|
||||
groupIndex = this.olv.GroupingStrategy.GetGroup(itemIndex);
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", groupIndex));
|
||||
}
|
||||
|
||||
public void GetItemGroupCount(int itemIndex, out int occurrenceCount) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroupCount({0})", itemIndex));
|
||||
occurrenceCount = 1;
|
||||
}
|
||||
|
||||
public void OnCacheHint(NativeMethods.LVITEMINDEX from, NativeMethods.LVITEMINDEX to) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("OnCacheHint({0}, {1}, {2}, {3})", from.iGroup, from.iItem, to.iGroup, to.iItem));
|
||||
this.olv.GroupingStrategy.CacheHint(from.iGroup, from.iItem, to.iGroup, to.iItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
343
ObjectListView/Implementation/VirtualListDataSource.cs
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* VirtualListDataSource - Encapsulate how data is provided to a virtual list
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 28/08/2009 11:10am
|
||||
*
|
||||
* Change log:
|
||||
* v2.4
|
||||
* 2010-04-01 JPP - Added IFilterableDataSource
|
||||
* v2.3
|
||||
* 2009-08-28 JPP - Initial version (Separated from VirtualObjectListView.cs)
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A VirtualListDataSource is a complete manner to provide functionality to a virtual list.
|
||||
/// An object that implements this interface provides a VirtualObjectListView with all the
|
||||
/// information it needs to be fully functional.
|
||||
/// </summary>
|
||||
/// <remarks>Implementors must provide functioning implementations of at least GetObjectCount()
|
||||
/// and GetNthObject(), otherwise nothing will appear in the list.</remarks>
|
||||
public interface IVirtualListDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the object that should be displayed at the n'th row.
|
||||
/// </summary>
|
||||
/// <param name="n">The index of the row whose object is to be returned.</param>
|
||||
/// <returns>The model object at the n'th row, or null if the fetching was unsuccessful.</returns>
|
||||
Object GetNthObject(int n);
|
||||
|
||||
/// <summary>
|
||||
/// Return the number of rows that should be visible in the virtual list
|
||||
/// </summary>
|
||||
/// <returns>The number of rows the list view should have.</returns>
|
||||
int GetObjectCount();
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the row that is showing the given model object
|
||||
/// </summary>
|
||||
/// <param name="model">The model object sought</param>
|
||||
/// <returns>The index of the row showing the model, or -1 if the object could not be found.</returns>
|
||||
int GetObjectIndex(Object model);
|
||||
|
||||
/// <summary>
|
||||
/// The ListView is about to request the given range of items. Do
|
||||
/// whatever caching seems appropriate.
|
||||
/// </summary>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
void PrepareCache(int first, int last);
|
||||
|
||||
/// <summary>
|
||||
/// Find the first row that "matches" the given text in the given range.
|
||||
/// </summary>
|
||||
/// <param name="value">The text typed by the user</param>
|
||||
/// <param name="first">Start searching from this index. This may be greater than the 'to' parameter,
|
||||
/// in which case the search should descend</param>
|
||||
/// <param name="last">Do not search beyond this index. This may be less than the 'from' parameter.</param>
|
||||
/// <param name="column">The column that should be considered when looking for a match.</param>
|
||||
/// <returns>Return the index of row that was matched, or -1 if no match was found</returns>
|
||||
int SearchText(string value, int first, int last, OLVColumn column);
|
||||
|
||||
/// <summary>
|
||||
/// Sort the model objects in the data source.
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="order"></param>
|
||||
void Sort(OLVColumn column, SortOrder order);
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
// Modification commands
|
||||
// THINK: Should we split these four into a separate interface?
|
||||
|
||||
/// <summary>
|
||||
/// Add the given collection of model objects to this control.
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">A collection of model objects</param>
|
||||
void AddObjects(ICollection modelObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Insert the given collection of model objects to this control at the position
|
||||
/// </summary>
|
||||
/// <param name="index">Index where the collection will be added</param>
|
||||
/// <param name="modelObjects">A collection of model objects</param>
|
||||
void InsertObjects(int index, ICollection modelObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all of the given objects from the control
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">Collection of objects to be removed</param>
|
||||
void RemoveObjects(ICollection modelObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Set the collection of objects that this control will show.
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
void SetObjects(IEnumerable collection);
|
||||
|
||||
/// <summary>
|
||||
/// Update/replace the nth object with the given object
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObject"></param>
|
||||
void UpdateObject(int index, object modelObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This extension allow virtual lists to filter their contents
|
||||
/// </summary>
|
||||
public interface IFilterableDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// All subsequent retrievals on this data source should be filtered
|
||||
/// through the given filters. null means no filtering of that kind.
|
||||
/// </summary>
|
||||
/// <param name="modelFilter"></param>
|
||||
/// <param name="listFilter"></param>
|
||||
void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A do-nothing implementation of the VirtualListDataSource interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Creates an AbstractVirtualListDataSource
|
||||
/// </remarks>
|
||||
/// <param name="listView"></param>
|
||||
public class AbstractVirtualListDataSource(VirtualObjectListView listView) : IVirtualListDataSource, IFilterableDataSource
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The list view that this data source is giving information to.
|
||||
/// </summary>
|
||||
protected VirtualObjectListView listView = listView;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
public virtual object GetNthObject(int n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual int GetObjectCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetObjectIndex(object model) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
public virtual void PrepareCache(int from, int to) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int SearchText(string value, int first, int last, OLVColumn column) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="order"></param>
|
||||
public virtual void Sort(OLVColumn column, SortOrder order) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
public virtual void AddObjects(ICollection modelObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObjects"></param>
|
||||
public virtual void InsertObjects(int index, ICollection modelObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
public virtual void RemoveObjects(ICollection modelObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public virtual void SetObjects(IEnumerable collection) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update/replace the nth object with the given object
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObject"></param>
|
||||
public virtual void UpdateObject(int index, object modelObject) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a useful default implementation of SearchText method, intended to be called
|
||||
/// by implementors of IVirtualListDataSource.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
static public int DefaultSearchText(string value, int first, int last, OLVColumn column, IVirtualListDataSource source) {
|
||||
if (first <= last) {
|
||||
for (int i = first; i <= last; i++) {
|
||||
string data = column.GetStringValue(source.GetNthObject(i));
|
||||
if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
for (int i = first; i >= last; i--) {
|
||||
string data = column.GetStringValue(source.GetNthObject(i));
|
||||
if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#region IFilterableDataSource Members
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelFilter"></param>
|
||||
/// <param name="listFilter"></param>
|
||||
virtual public void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class mimics the behavior of VirtualObjectListView v1.x.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Creates a VirtualListVersion1DataSource
|
||||
/// </remarks>
|
||||
/// <param name="listView"></param>
|
||||
public class VirtualListVersion1DataSource(VirtualObjectListView listView) : AbstractVirtualListDataSource(listView)
|
||||
{
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// How will the n'th object of the data source be fetched?
|
||||
/// </summary>
|
||||
public RowGetterDelegate RowGetter {
|
||||
get { return rowGetter; }
|
||||
set { rowGetter = value; }
|
||||
}
|
||||
private RowGetterDelegate rowGetter;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IVirtualListDataSource implementation
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
public override object GetNthObject(int n) {
|
||||
if (this.RowGetter == null)
|
||||
return null;
|
||||
else
|
||||
return this.RowGetter(n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public override int SearchText(string value, int first, int last, OLVColumn column) {
|
||||
return DefaultSearchText(value, first, last, column, this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1911
ObjectListView/OLVColumn.cs
Normal file
536
ObjectListView/ObjectListView.DesignTime.cs
Normal file
@@ -0,0 +1,536 @@
|
||||
/*
|
||||
* DesignSupport - Design time support for the various classes within ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 12/08/2009 8:36 PM
|
||||
*
|
||||
* Change log:
|
||||
* 2012-08-27 JPP - Fall back to more specific type name for the ListViewDesigner if
|
||||
* the first GetType() fails.
|
||||
* v2.5.1
|
||||
* 2012-04-26 JPP - Filter group events from TreeListView since it can't have groups
|
||||
* 2011-06-06 JPP - Vastly improved ObjectListViewDesigner, based off information in
|
||||
* "'Inheriting' from an Internal WinForms Designer" on CodeProject.
|
||||
* v2.3
|
||||
* 2009-08-12 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Forms.Design;
|
||||
|
||||
namespace BrightIdeasSoftware.Design
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Designer for <see cref="ObjectListView"/> and its subclasses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This designer removes properties and events that are available on ListView but that are not
|
||||
/// useful on ObjectListView.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// We can't inherit from System.Windows.Forms.Design.ListViewDesigner, since it is marked internal.
|
||||
/// So, this class uses reflection to create a ListViewDesigner and then forwards messages to that designer.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class ObjectListViewDesigner : ControlDesigner
|
||||
{
|
||||
|
||||
#region Initialize & Dispose
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the designer with the specified component.
|
||||
/// </summary>
|
||||
/// <param name="component">The <see cref="T:System.ComponentModel.IComponent"/> to associate the designer with. This component must always be an instance of, or derive from, <see cref="T:System.Windows.Forms.Control"/>. </param>
|
||||
public override void Initialize(IComponent component) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.Initialize");
|
||||
|
||||
// Use reflection to bypass the "internal" marker on ListViewDesigner
|
||||
// If we can't get the unversioned designer, look specifically for .NET 4.0 version of it.
|
||||
Type tListViewDesigner = Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design") ??
|
||||
Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design, " +
|
||||
"Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
||||
if (tListViewDesigner == null) throw new ArgumentException("Could not load ListViewDesigner");
|
||||
|
||||
this.listViewDesigner = (ControlDesigner)Activator.CreateInstance(tListViewDesigner, BindingFlags.Instance | BindingFlags.Public, null, null, null);
|
||||
this.designerFilter = this.listViewDesigner;
|
||||
|
||||
// Fetch the methods from the ListViewDesigner that we know we want to use
|
||||
this.listViewDesignGetHitTest = tListViewDesigner.GetMethod("GetHitTest", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
this.listViewDesignWndProc = tListViewDesigner.GetMethod("WndProc", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Debug.Assert(this.listViewDesignGetHitTest != null, "Required method (GetHitTest) not found on ListViewDesigner");
|
||||
Debug.Assert(this.listViewDesignWndProc != null, "Required method (WndProc) not found on ListViewDesigner");
|
||||
|
||||
// Tell the Designer to use properties of default designer as well as the properties of this class (do before base.Initialize)
|
||||
TypeDescriptor.CreateAssociation(component, this.listViewDesigner);
|
||||
|
||||
IServiceContainer site = (IServiceContainer)component.Site;
|
||||
if (site != null && GetService(typeof(DesignerCommandSet)) == null) {
|
||||
site.AddService(typeof(DesignerCommandSet), new CDDesignerCommandSet(this));
|
||||
} else {
|
||||
Debug.Fail("site != null && GetService(typeof (DesignerCommandSet)) == null");
|
||||
}
|
||||
|
||||
this.listViewDesigner.Initialize(component);
|
||||
base.Initialize(component);
|
||||
|
||||
RemoveDuplicateDockingActionList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a newly created component.
|
||||
/// </summary>
|
||||
/// <param name="defaultValues">A name/value dictionary of default values to apply to properties. May be null if no default values are specified.</param>
|
||||
public override void InitializeNewComponent(IDictionary defaultValues) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.InitializeNewComponent");
|
||||
base.InitializeNewComponent(defaultValues);
|
||||
this.listViewDesigner.InitializeNewComponent(defaultValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Design.ControlDesigner"/> and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.Dispose");
|
||||
if (disposing) {
|
||||
if (this.listViewDesigner != null) {
|
||||
this.listViewDesigner.Dispose();
|
||||
// Normally we would now null out the designer, but this designer
|
||||
// still has methods called AFTER it is disposed.
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the duplicate DockingActionList added by this designer to the <see cref="DesignerActionService"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="ControlDesigner.Initialize"/> adds an internal DockingActionList : 'Dock/Undock in Parent Container'.
|
||||
/// But the default designer has already added that action list. So we need to remove one.
|
||||
/// </remarks>
|
||||
private void RemoveDuplicateDockingActionList() {
|
||||
// This is a true hack -- in a class that is basically a huge hack itself.
|
||||
// Reach into the bowel of our base class, get a private field, and use that fields value to
|
||||
// remove an action from the designer.
|
||||
// In ControlDesigner, there is "private DockingActionList dockingAction;"
|
||||
// Don't you just love Reflector?!
|
||||
FieldInfo fi = typeof(ControlDesigner).GetField("dockingAction", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (fi != null) {
|
||||
DesignerActionList dockingAction = (DesignerActionList)fi.GetValue(this);
|
||||
if (dockingAction != null) {
|
||||
DesignerActionService service = (DesignerActionService)GetService(typeof(DesignerActionService));
|
||||
if (service != null) {
|
||||
service.Remove(this.Control, dockingAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDesignerFilter overrides
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the set of properties the component exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="properties">An <see cref="T:System.Collections.IDictionary"/> containing the properties for the class of the component. </param>
|
||||
protected override void PreFilterProperties(IDictionary properties) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.PreFilterProperties");
|
||||
|
||||
// Always call the base PreFilterProperties implementation
|
||||
// before you modify the properties collection.
|
||||
base.PreFilterProperties(properties);
|
||||
|
||||
// Give the listviewdesigner a chance to filter the properties
|
||||
// (though we already know it's not going to do anything)
|
||||
this.designerFilter.PreFilterProperties(properties);
|
||||
|
||||
// I'd like to just remove the redundant properties, but that would
|
||||
// break backward compatibility. The deserialiser that handles the XXX.Designer.cs file
|
||||
// works off the designer, so even if the property exists in the class, the deserialiser will
|
||||
// throw an error if the associated designer actually removes that property.
|
||||
// So we shadow the unwanted properties, and give the replacement properties
|
||||
// non-browsable attributes so that they are hidden from the user
|
||||
|
||||
List<string> unwantedProperties = new List<string>(new string[] {
|
||||
"BackgroundImage", "BackgroundImageTiled", "HotTracking", "HoverSelection",
|
||||
"LabelEdit", "VirtualListSize", "VirtualMode" });
|
||||
|
||||
// Also hid Tooltip properties, since giving a tooltip to the control through the IDE
|
||||
// messes up the tooltip handling
|
||||
foreach (string propertyName in properties.Keys) {
|
||||
if (propertyName.StartsWith("ToolTip")) {
|
||||
unwantedProperties.Add(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are looking at a TreeListView, remove group related properties
|
||||
// since TreeListViews can't show groups
|
||||
if (this.Control is TreeListView) {
|
||||
unwantedProperties.AddRange(new string[] {
|
||||
"GroupImageList", "GroupWithItemCountFormat", "GroupWithItemCountSingularFormat", "HasCollapsibleGroups",
|
||||
"SpaceBetweenGroups", "ShowGroups", "SortGroupItemsByPrimaryColumn", "ShowItemCountOnGroups"
|
||||
});
|
||||
}
|
||||
|
||||
// Shadow the unwanted properties, and give the replacement properties
|
||||
// non-browsable attributes so that they are hidden from the user
|
||||
foreach (string unwantedProperty in unwantedProperties) {
|
||||
PropertyDescriptor propertyDesc = TypeDescriptor.CreateProperty(
|
||||
typeof(ObjectListView),
|
||||
(PropertyDescriptor)properties[unwantedProperty],
|
||||
new BrowsableAttribute(false));
|
||||
properties[unwantedProperty] = propertyDesc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows a designer to add to the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="events">The events for the class of the component. </param>
|
||||
protected override void PreFilterEvents(IDictionary events) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.PreFilterEvents");
|
||||
base.PreFilterEvents(events);
|
||||
this.designerFilter.PreFilterEvents(events);
|
||||
|
||||
// Remove the events that don't make sense for an ObjectListView.
|
||||
// See PreFilterProperties() for why we do this dance rather than just remove the event.
|
||||
List<string> unwanted = new List<string>(new string[] {
|
||||
"AfterLabelEdit",
|
||||
"BeforeLabelEdit",
|
||||
"DrawColumnHeader",
|
||||
"DrawItem",
|
||||
"DrawSubItem",
|
||||
"RetrieveVirtualItem",
|
||||
"SearchForVirtualItem",
|
||||
"VirtualItemsSelectionRangeChanged"
|
||||
});
|
||||
|
||||
// If we are looking at a TreeListView, remove group related events
|
||||
// since TreeListViews can't show groups
|
||||
if (this.Control is TreeListView) {
|
||||
unwanted.AddRange(new string[] {
|
||||
"AboutToCreateGroups",
|
||||
"AfterCreatingGroups",
|
||||
"BeforeCreatingGroups",
|
||||
"GroupTaskClicked",
|
||||
"GroupExpandingCollapsing",
|
||||
"GroupStateChanged"
|
||||
});
|
||||
}
|
||||
|
||||
foreach (string unwantedEvent in unwanted) {
|
||||
EventDescriptor eventDesc = TypeDescriptor.CreateEvent(
|
||||
typeof(ObjectListView),
|
||||
(EventDescriptor)events[unwantedEvent],
|
||||
new BrowsableAttribute(false));
|
||||
events[unwantedEvent] = eventDesc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows a designer to change or remove items from the set of attributes that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributes">The attributes for the class of the component. </param>
|
||||
protected override void PostFilterAttributes(IDictionary attributes) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.PostFilterAttributes");
|
||||
this.designerFilter.PostFilterAttributes(attributes);
|
||||
base.PostFilterAttributes(attributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows a designer to change or remove items from the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="events">The events for the class of the component. </param>
|
||||
protected override void PostFilterEvents(IDictionary events) {
|
||||
// Debug.WriteLine("ObjectListViewDesigner.PostFilterEvents");
|
||||
this.designerFilter.PostFilterEvents(events);
|
||||
base.PostFilterEvents(events);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
/// <summary>
|
||||
/// Gets the design-time action lists supported by the component associated with the designer.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The design-time action lists supported by the component associated with the designer.
|
||||
/// </returns>
|
||||
public override DesignerActionListCollection ActionLists {
|
||||
get {
|
||||
// We want to change the first action list so it only has the commands we want
|
||||
DesignerActionListCollection actionLists = this.listViewDesigner.ActionLists;
|
||||
if (actionLists.Count > 0 && !(actionLists[0] is ListViewActionListAdapter)) {
|
||||
actionLists[0] = new ListViewActionListAdapter(this, actionLists[0]);
|
||||
}
|
||||
return actionLists;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of components associated with the component managed by the designer.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The components that are associated with the component managed by the designer.
|
||||
/// </returns>
|
||||
public override ICollection AssociatedComponents {
|
||||
get {
|
||||
ArrayList components = new ArrayList(base.AssociatedComponents);
|
||||
components.AddRange(this.listViewDesigner.AssociatedComponents);
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a mouse click at the specified point should be handled by the control.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if a click at the specified point is to be handled by the control; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="point">A <see cref="T:System.Drawing.Point"/> indicating the position at which the mouse was clicked, in screen coordinates. </param>
|
||||
protected override bool GetHitTest(Point point) {
|
||||
// The ListViewDesigner wants to allow column dividers to be resized
|
||||
return (bool)this.listViewDesignGetHitTest.Invoke(listViewDesigner, new object[] { point });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes Windows messages and optionally routes them to the control.
|
||||
/// </summary>
|
||||
/// <param name="m">The <see cref="T:System.Windows.Forms.Message"/> to process. </param>
|
||||
protected override void WndProc(ref Message m) {
|
||||
switch (m.Msg) {
|
||||
case 0x4e:
|
||||
case 0x204e:
|
||||
// The listview designer is interested in HDN_ENDTRACK notifications
|
||||
this.listViewDesignWndProc.Invoke(listViewDesigner, new object[] { m });
|
||||
break;
|
||||
default:
|
||||
base.WndProc(ref m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation variables
|
||||
|
||||
private ControlDesigner listViewDesigner;
|
||||
private IDesignerFilter designerFilter;
|
||||
private MethodInfo listViewDesignGetHitTest;
|
||||
private MethodInfo listViewDesignWndProc;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Custom action list
|
||||
|
||||
/// <summary>
|
||||
/// This class modifies a ListViewActionList, by removing the "Edit Items" and "Edit Groups" actions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// That class is internal, so we cannot simply subclass it, which would be simplier.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Action lists use reflection to determine if that action can be executed, so we not
|
||||
/// only have to modify the returned collection of actions, but we have to implement
|
||||
/// the properties and commands that the returned actions use. </para>
|
||||
/// </remarks>
|
||||
private class ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList) : DesignerActionList(wrappedList.Component)
|
||||
{
|
||||
public override DesignerActionItemCollection GetSortedActionItems() {
|
||||
DesignerActionItemCollection items = wrappedList.GetSortedActionItems();
|
||||
items.RemoveAt(2); // remove Edit Groups
|
||||
items.RemoveAt(0); // remove Edit Items
|
||||
return items;
|
||||
}
|
||||
|
||||
private void EditValue(ComponentDesigner componentDesigner, IComponent iComponent, string propertyName) {
|
||||
// One more complication. The ListViewActionList classes uses an internal class, EditorServiceContext, to
|
||||
// edit the items/columns/groups collections. So, we use reflection to bypass the data hiding.
|
||||
Type tEditorServiceContext = Type.GetType("System.Windows.Forms.Design.EditorServiceContext, System.Design");
|
||||
tEditorServiceContext.InvokeMember("EditValue", BindingFlags.InvokeMethod | BindingFlags.Static, null, null, new object[] { componentDesigner, iComponent, propertyName });
|
||||
}
|
||||
|
||||
private void SetValue(object target, string propertyName, object value) {
|
||||
TypeDescriptor.GetProperties(target)[propertyName].SetValue(target, value);
|
||||
}
|
||||
|
||||
public void InvokeColumnsDialog() {
|
||||
EditValue(this.designer, base.Component, "Columns");
|
||||
}
|
||||
|
||||
// Don't need these since we removed their corresponding actions from the list.
|
||||
// Keep the methods just in case.
|
||||
|
||||
//public void InvokeGroupsDialog() {
|
||||
// EditValue(this.designer, base.Component, "Groups");
|
||||
//}
|
||||
|
||||
//public void InvokeItemsDialog() {
|
||||
// EditValue(this.designer, base.Component, "Items");
|
||||
//}
|
||||
|
||||
public ImageList LargeImageList {
|
||||
get { return ((ListView)base.Component).LargeImageList; }
|
||||
set { SetValue(base.Component, "LargeImageList", value); }
|
||||
}
|
||||
|
||||
public ImageList SmallImageList {
|
||||
get { return ((ListView)base.Component).SmallImageList; }
|
||||
set { SetValue(base.Component, "SmallImageList", value); }
|
||||
}
|
||||
|
||||
public View View {
|
||||
get { return ((ListView)base.Component).View; }
|
||||
set { SetValue(base.Component, "View", value); }
|
||||
}
|
||||
|
||||
ObjectListViewDesigner designer = designer;
|
||||
DesignerActionList wrappedList = wrappedList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DesignerCommandSet
|
||||
|
||||
private class CDDesignerCommandSet(ComponentDesigner componentDesigner) : DesignerCommandSet
|
||||
{
|
||||
public override ICollection GetCommands(string name) {
|
||||
// Debug.WriteLine("CDDesignerCommandSet.GetCommands:" + name);
|
||||
if (componentDesigner != null) {
|
||||
if (name.Equals("Verbs")) {
|
||||
return componentDesigner.Verbs;
|
||||
}
|
||||
if (name.Equals("ActionLists")) {
|
||||
return componentDesigner.ActionLists;
|
||||
}
|
||||
}
|
||||
return base.GetCommands(name);
|
||||
}
|
||||
|
||||
private readonly ComponentDesigner componentDesigner = componentDesigner;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class works in conjunction with the OLVColumns property to allow OLVColumns
|
||||
/// to be added to the ObjectListView.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Create a OLVColumnCollectionEditor
|
||||
/// </remarks>
|
||||
/// <param name="t"></param>
|
||||
public class OLVColumnCollectionEditor(Type t) : System.ComponentModel.Design.CollectionEditor(t)
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// What type of object does this editor create?
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Type CreateCollectionItemType() {
|
||||
return typeof(OLVColumn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit a given value
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
|
||||
if (context == null)
|
||||
throw new ArgumentNullException("context");
|
||||
if (provider == null)
|
||||
throw new ArgumentNullException("provider");
|
||||
|
||||
// Figure out which ObjectListView we are working on. This should be the Instance of the context.
|
||||
ObjectListView olv = context.Instance as ObjectListView;
|
||||
Debug.Assert(olv != null, "Instance must be an ObjectListView");
|
||||
|
||||
// Edit all the columns, not just the ones that are visible
|
||||
base.EditValue(context, provider, olv.AllColumns);
|
||||
|
||||
// Set the columns on the ListView to just the visible columns
|
||||
List<OLVColumn> newColumns = olv.GetFilteredColumns(View.Details);
|
||||
olv.Columns.Clear();
|
||||
olv.Columns.AddRange(newColumns.ToArray());
|
||||
|
||||
return olv.Columns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What text should be shown in the list for the given object?
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
protected override string GetDisplayText(object value) {
|
||||
OLVColumn col = value as OLVColumn;
|
||||
if (col == null || String.IsNullOrEmpty(col.AspectName))
|
||||
return base.GetDisplayText(value);
|
||||
|
||||
return String.Format("{0} ({1})", base.GetDisplayText(value), col.AspectName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control how the overlay is presented in the IDE
|
||||
/// </summary>
|
||||
internal class OverlayConverter : ExpandableObjectConverter
|
||||
{
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
|
||||
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) {
|
||||
if (destinationType == typeof(string)) {
|
||||
ImageOverlay imageOverlay = value as ImageOverlay;
|
||||
if (imageOverlay != null) {
|
||||
return imageOverlay.Image == null ? "(none)" : "(set)";
|
||||
}
|
||||
TextOverlay textOverlay = value as TextOverlay;
|
||||
if (textOverlay != null) {
|
||||
return String.IsNullOrEmpty(textOverlay.Text) ? "(none)" : "(set)";
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
ObjectListView/ObjectListView.NetCore.csproj
Normal file
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<Deterministic>false</Deterministic>
|
||||
<RootNamespace>BrightIdeasSoftware</RootNamespace>
|
||||
<AssemblyName>ObjectListView</AssemblyName>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
|
||||
<NoWarn>$(NoWarn);WFO1000</NoWarn>
|
||||
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' == 'x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' == 'ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="CustomDictionary.xml" Link="CustomDictionary.xml" />
|
||||
<Content Include="Resources\clear-filter.png" Link="Resources\clear-filter.png" />
|
||||
<Content Include="Resources\coffee.jpg" Link="Resources\coffee.jpg" />
|
||||
<Content Include="Resources\filter-icons3.png" Link="Resources\filter-icons3.png" />
|
||||
<Content Include="Resources\filter.png" Link="Resources\filter.png" />
|
||||
<Content Include="Resources\sort-ascending.png" Link="Resources\sort-ascending.png" />
|
||||
<Content Include="Resources\sort-descending.png" Link="Resources\sort-descending.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="CellEditing\" />
|
||||
<Folder Include="DragDrop\" />
|
||||
<Folder Include="Filtering\" />
|
||||
<Folder Include="Implementation\" />
|
||||
<Folder Include="Utilities\" />
|
||||
<Folder Include="SubControls\" />
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Rendering\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
11813
ObjectListView/ObjectListView.cs
Normal file
37
ObjectListView/Package.nuspec
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
|
||||
<metadata minClientVersion="2.12">
|
||||
<id>ObjectListView.Updated</id>
|
||||
<title>ObjectListView (Updated)</title>
|
||||
<version>$version$</version>
|
||||
<authors>Phillip Piper</authors>
|
||||
<owners>$author$</owners>
|
||||
<license type="file">LICENSE</license>
|
||||
<icon>.editoricon.png</icon>
|
||||
<projectUrl>https://github.com/ennerperez/ObjectListView</projectUrl>
|
||||
<description>
|
||||
ObjectListView is a .NET ListView wired on caffeine, guarana and steroids.
|
||||
More calmly, it is a C# wrapper around a .NET ListView, which makes the ListView much easier to use and teaches it lots of neat new tricks.
|
||||
</description>
|
||||
<summary>$description$</summary>
|
||||
<copyright>$copyright$</copyright>
|
||||
<tags>.Net WinForms ListView Controls</tags>
|
||||
<repository type="git" url="https://github.com/ennerperez/ObjectListView" />
|
||||
<dependencies>
|
||||
<group targetFramework=".NETFramework4.0" />
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="System.Drawing.Common" version="4.7.0" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\.editoricon.png" target=".editoricon.png" />
|
||||
<file src="..\..\README.md" target="README.md" />
|
||||
<file src="..\..\CHANGELOG.md" target="CHANGELOG.md" />
|
||||
<file src="..\..\LICENSE" target="LICENSE" />
|
||||
<!-- NETFX -->
|
||||
<file src="..\ObjectListView\bin\release\ObjectListView.dll" target="lib\net40\ObjectListView.dll" />
|
||||
<!-- NETCORE -->
|
||||
<file src="..\ObjectListView.NetCore\bin\release\netcoreapp3.1\ObjectListView.dll" target="lib\netstandard2.0\ObjectListView.dll" />
|
||||
</files>
|
||||
</package>
|
||||
22
ObjectListView/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ObjectListView")]
|
||||
[assembly: AssemblyDescription("A much easier to use ListView and friends")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Bright Ideas Software")]
|
||||
[assembly: AssemblyProduct("ObjectListView")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2006-2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ef28c7a8-77ae-442d-abc3-bb023fa31e57")]
|
||||
113
ObjectListView/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,113 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.18444
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace BrightIdeasSoftware.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BrightIdeasSoftware.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap ClearFiltering {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("ClearFiltering", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap ColumnFilterIndicator {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("ColumnFilterIndicator", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Filtering {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Filtering", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap SortAscending {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("SortAscending", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap SortDescending {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("SortDescending", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,30 +112,26 @@
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="PropertyDescriptionColors" xml:space="preserve">
|
||||
<value>Select the color quality to be used.</value>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="ClearFiltering" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\clear-filter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Colors" xml:space="preserve">
|
||||
<value>Colors</value>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="ColumnFilterIndicator" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\filter-icons3.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Rdp16777216Colors" xml:space="preserve">
|
||||
<value>16777216 Colors (24-bit)</value>
|
||||
<data name="Filtering" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\filter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Rdp256Colors" xml:space="preserve">
|
||||
<value>256 Colors (8-bit)</value>
|
||||
<data name="SortAscending" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\sort-ascending.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Rdp32768Colors" xml:space="preserve">
|
||||
<value>32768 Colors (15-bit)</value>
|
||||
</data>
|
||||
<data name="Rdp4294967296Colors" xml:space="preserve">
|
||||
<value>16777216 Colors (32-bit)</value>
|
||||
</data>
|
||||
<data name="Rdp65536Colors" xml:space="preserve">
|
||||
<value>65536 Colors (16-bit)</value>
|
||||
<data name="SortDescending" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\sort-descending.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
||||
743
ObjectListView/Rendering/Adornments.cs
Normal file
@@ -0,0 +1,743 @@
|
||||
/*
|
||||
* Adornments - Adornments are the basis for overlays and decorations -- things that can be rendered over the top of a ListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 16/08/2009 1:02 AM
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-08-18 JPP - Correctly dispose of brush and pen resources
|
||||
* v2.3
|
||||
* 2009-09-22 JPP - Added Wrap property to TextAdornment, to allow text wrapping to be disabled
|
||||
* - Added ShrinkToWidth property to ImageAdornment
|
||||
* 2009-08-17 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
* - Use IPointLocator rather than Corners
|
||||
* - Add RotationCenter property ratherr than always using middle center
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// An adorment is the common base for overlays and decorations.
|
||||
/// </summary>
|
||||
public class GraphicAdornment
|
||||
{
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the corner of the adornment that will be positioned at the reference corner
|
||||
/// </summary>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public System.Drawing.ContentAlignment AdornmentCorner {
|
||||
get { return this.adornmentCorner; }
|
||||
set { this.adornmentCorner = value; }
|
||||
}
|
||||
private System.Drawing.ContentAlignment adornmentCorner = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets location within the reference rectange where the adornment will be drawn
|
||||
/// </summary>
|
||||
/// <remarks>This is a simplied interface to ReferenceCorner and AdornmentCorner </remarks>
|
||||
[Category("ObjectListView"),
|
||||
Description("How will the adornment be aligned"),
|
||||
DefaultValue(System.Drawing.ContentAlignment.BottomRight),
|
||||
NotifyParentProperty(true)]
|
||||
public System.Drawing.ContentAlignment Alignment {
|
||||
get { return this.alignment; }
|
||||
set {
|
||||
this.alignment = value;
|
||||
this.ReferenceCorner = value;
|
||||
this.AdornmentCorner = value;
|
||||
}
|
||||
}
|
||||
private System.Drawing.ContentAlignment alignment = System.Drawing.ContentAlignment.BottomRight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset by which the position of the adornment will be adjusted
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The offset by which the position of the adornment will be adjusted"),
|
||||
DefaultValue(typeof(Size), "0,0")]
|
||||
public Size Offset {
|
||||
get { return this.offset; }
|
||||
set { this.offset = value; }
|
||||
}
|
||||
private Size offset = new Size();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the point of the reference rectangle to which the adornment will be aligned.
|
||||
/// </summary>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public System.Drawing.ContentAlignment ReferenceCorner {
|
||||
get { return this.referenceCorner; }
|
||||
set { this.referenceCorner = value; }
|
||||
}
|
||||
private System.Drawing.ContentAlignment referenceCorner = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the degree of rotation by which the adornment will be transformed.
|
||||
/// The centre of rotation will be the center point of the adornment.
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The degree of rotation that will be applied to the adornment."),
|
||||
DefaultValue(0),
|
||||
NotifyParentProperty(true)]
|
||||
public int Rotation {
|
||||
get { return this.rotation; }
|
||||
set { this.rotation = value; }
|
||||
}
|
||||
private int rotation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transparency of the overlay.
|
||||
/// 0 is completely transparent, 255 is completely opaque.
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The transparency of this adornment. 0 is completely transparent, 255 is completely opaque."),
|
||||
DefaultValue(128)]
|
||||
public int Transparency {
|
||||
get { return this.transparency; }
|
||||
set { this.transparency = Math.Min(255, Math.Max(0, value)); }
|
||||
}
|
||||
private int transparency = 128;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Calculations
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the location of rectangle of the given size,
|
||||
/// so that it's indicated corner would be at the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt">The point</param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="corner">Which corner will be positioned at the reference point</param>
|
||||
/// <returns></returns>
|
||||
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.TopLeft) -> Point(50, 100)</example>
|
||||
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.MiddleCenter) -> Point(45, 90)</example>
|
||||
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.BottomRight) -> Point(40, 80)</example>
|
||||
public virtual Point CalculateAlignedPosition(Point pt, Size size, System.Drawing.ContentAlignment corner) {
|
||||
switch (corner) {
|
||||
case System.Drawing.ContentAlignment.TopLeft:
|
||||
return pt;
|
||||
case System.Drawing.ContentAlignment.TopCenter:
|
||||
return new Point(pt.X - (size.Width / 2), pt.Y);
|
||||
case System.Drawing.ContentAlignment.TopRight:
|
||||
return new Point(pt.X - size.Width, pt.Y);
|
||||
case System.Drawing.ContentAlignment.MiddleLeft:
|
||||
return new Point(pt.X, pt.Y - (size.Height / 2));
|
||||
case System.Drawing.ContentAlignment.MiddleCenter:
|
||||
return new Point(pt.X - (size.Width / 2), pt.Y - (size.Height / 2));
|
||||
case System.Drawing.ContentAlignment.MiddleRight:
|
||||
return new Point(pt.X - size.Width, pt.Y - (size.Height / 2));
|
||||
case System.Drawing.ContentAlignment.BottomLeft:
|
||||
return new Point(pt.X, pt.Y - size.Height);
|
||||
case System.Drawing.ContentAlignment.BottomCenter:
|
||||
return new Point(pt.X - (size.Width / 2), pt.Y - size.Height);
|
||||
case System.Drawing.ContentAlignment.BottomRight:
|
||||
return new Point(pt.X - size.Width, pt.Y - size.Height);
|
||||
}
|
||||
|
||||
// Should never reach here
|
||||
return pt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate a rectangle that has the given size which is positioned so that
|
||||
/// its alignment point is at the reference location of the given rect.
|
||||
/// </summary>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="sz"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz) {
|
||||
return this.CreateAlignedRectangle(r, sz, this.ReferenceCorner, this.AdornmentCorner, this.Offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a rectangle of the given size which is positioned so that
|
||||
/// its indicated corner is at the indicated corner of the reference rect.
|
||||
/// </summary>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="sz"></param>
|
||||
/// <param name="corner"></param>
|
||||
/// <param name="referenceCorner"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// <para>Creates a rectangle so that its bottom left is at the centre of the reference:
|
||||
/// corner=BottomLeft, referenceCorner=MiddleCenter</para>
|
||||
/// <para>This is a powerful concept that takes some getting used to, but is
|
||||
/// very neat once you understand it.</para>
|
||||
/// </remarks>
|
||||
public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz,
|
||||
System.Drawing.ContentAlignment corner, System.Drawing.ContentAlignment referenceCorner, Size offset) {
|
||||
Point referencePt = this.CalculateCorner(r, referenceCorner);
|
||||
Point topLeft = this.CalculateAlignedPosition(referencePt, sz, corner);
|
||||
return new Rectangle(topLeft + offset, sz);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the point at the indicated corner of the given rectangle (it doesn't
|
||||
/// have to be a corner, but a named location)
|
||||
/// </summary>
|
||||
/// <param name="r">The reference rectangle</param>
|
||||
/// <param name="corner">Which point of the rectangle should be returned?</param>
|
||||
/// <returns>A point</returns>
|
||||
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.TopLeft) -> Point(0, 0)</example>
|
||||
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.MiddleCenter) -> Point(25, 50)</example>
|
||||
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.BottomRight) -> Point(50, 100)</example>
|
||||
public virtual Point CalculateCorner(Rectangle r, System.Drawing.ContentAlignment corner) {
|
||||
switch (corner) {
|
||||
case System.Drawing.ContentAlignment.TopLeft:
|
||||
return new Point(r.Left, r.Top);
|
||||
case System.Drawing.ContentAlignment.TopCenter:
|
||||
return new Point(r.X + (r.Width / 2), r.Top);
|
||||
case System.Drawing.ContentAlignment.TopRight:
|
||||
return new Point(r.Right, r.Top);
|
||||
case System.Drawing.ContentAlignment.MiddleLeft:
|
||||
return new Point(r.Left, r.Top + (r.Height / 2));
|
||||
case System.Drawing.ContentAlignment.MiddleCenter:
|
||||
return new Point(r.X + (r.Width / 2), r.Top + (r.Height / 2));
|
||||
case System.Drawing.ContentAlignment.MiddleRight:
|
||||
return new Point(r.Right, r.Top + (r.Height / 2));
|
||||
case System.Drawing.ContentAlignment.BottomLeft:
|
||||
return new Point(r.Left, r.Bottom);
|
||||
case System.Drawing.ContentAlignment.BottomCenter:
|
||||
return new Point(r.X + (r.Width / 2), r.Bottom);
|
||||
case System.Drawing.ContentAlignment.BottomRight:
|
||||
return new Point(r.Right, r.Bottom);
|
||||
}
|
||||
|
||||
// Should never reach here
|
||||
return r.Location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the item and the subitem, calculate its bounds.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="subItem"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Rectangle CalculateItemBounds(OLVListItem item, OLVListSubItem subItem) {
|
||||
if (item == null)
|
||||
return Rectangle.Empty;
|
||||
|
||||
if (subItem == null)
|
||||
return item.Bounds;
|
||||
|
||||
return item.GetSubItemBounds(item.SubItems.IndexOf(subItem));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Apply any specified rotation to the Graphic content.
|
||||
/// </summary>
|
||||
/// <param name="g">The Graphics to be transformed</param>
|
||||
/// <param name="r">The rotation will be around the centre of this rect</param>
|
||||
protected virtual void ApplyRotation(Graphics g, Rectangle r) {
|
||||
if (this.Rotation == 0)
|
||||
return;
|
||||
|
||||
// THINK: Do we want to reset the transform? I think we want to push a new transform
|
||||
g.ResetTransform();
|
||||
Matrix m = new Matrix();
|
||||
m.RotateAt(this.Rotation, new Point(r.Left + r.Width / 2, r.Top + r.Height / 2));
|
||||
g.Transform = m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverse the rotation created by ApplyRotation()
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
protected virtual void UnapplyRotation(Graphics g) {
|
||||
if (this.Rotation != 0)
|
||||
g.ResetTransform();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overlay that will draw an image over the top of the ObjectListView
|
||||
/// </summary>
|
||||
public class ImageAdornment : GraphicAdornment
|
||||
{
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image that will be drawn
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The image that will be drawn"),
|
||||
DefaultValue(null),
|
||||
NotifyParentProperty(true)]
|
||||
public Image Image {
|
||||
get { return this.image; }
|
||||
set { this.image = value; }
|
||||
}
|
||||
private Image image;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the image will be shrunk to fit with its horizontal bounds
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("Will the image be shrunk to fit within its width?"),
|
||||
DefaultValue(false)]
|
||||
public bool ShrinkToWidth {
|
||||
get { return this.shrinkToWidth; }
|
||||
set { this.shrinkToWidth = value; }
|
||||
}
|
||||
private bool shrinkToWidth;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Draw the image in its specified location
|
||||
/// </summary>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
public virtual void DrawImage(Graphics g, Rectangle r) {
|
||||
if (this.ShrinkToWidth)
|
||||
this.DrawScaledImage(g, r, this.Image, this.Transparency);
|
||||
else
|
||||
this.DrawImage(g, r, this.Image, this.Transparency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the image in its specified location
|
||||
/// </summary>
|
||||
/// <param name="image">The image to be drawn</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
|
||||
public virtual void DrawImage(Graphics g, Rectangle r, Image image, int transparency) {
|
||||
if (image != null)
|
||||
this.DrawImage(g, r, image, image.Size, transparency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the image in its specified location
|
||||
/// </summary>
|
||||
/// <param name="image">The image to be drawn</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
/// <param name="sz">How big should the image be?</param>
|
||||
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
|
||||
public virtual void DrawImage(Graphics g, Rectangle r, Image image, Size sz, int transparency) {
|
||||
if (image == null)
|
||||
return;
|
||||
|
||||
Rectangle adornmentBounds = this.CreateAlignedRectangle(r, sz);
|
||||
try {
|
||||
this.ApplyRotation(g, adornmentBounds);
|
||||
this.DrawTransparentBitmap(g, adornmentBounds, image, transparency);
|
||||
}
|
||||
finally {
|
||||
this.UnapplyRotation(g);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the image in its specified location, scaled so that it is not wider
|
||||
/// than the given rectangle. Height is scaled proportional to the width.
|
||||
/// </summary>
|
||||
/// <param name="image">The image to be drawn</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
|
||||
public virtual void DrawScaledImage(Graphics g, Rectangle r, Image image, int transparency) {
|
||||
if (image == null)
|
||||
return;
|
||||
|
||||
// If the image is too wide to be drawn in the space provided, proportionally scale it down.
|
||||
// Too tall images are not scaled.
|
||||
Size size = image.Size;
|
||||
if (image.Width > r.Width) {
|
||||
float scaleRatio = (float)r.Width / (float)image.Width;
|
||||
size.Height = (int)((float)image.Height * scaleRatio);
|
||||
size.Width = r.Width - 1;
|
||||
}
|
||||
|
||||
this.DrawImage(g, r, image, size, transparency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility to draw a bitmap transparenly.
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="transparency"></param>
|
||||
protected virtual void DrawTransparentBitmap(Graphics g, Rectangle r, Image image, int transparency) {
|
||||
ImageAttributes imageAttributes = null;
|
||||
if (transparency != 255) {
|
||||
imageAttributes = new ImageAttributes();
|
||||
float a = (float)transparency / 255.0f;
|
||||
float[][] colorMatrixElements = {
|
||||
new float[] {1, 0, 0, 0, 0},
|
||||
new float[] {0, 1, 0, 0, 0},
|
||||
new float[] {0, 0, 1, 0, 0},
|
||||
new float[] {0, 0, 0, a, 0},
|
||||
new float[] {0, 0, 0, 0, 1}};
|
||||
|
||||
imageAttributes.SetColorMatrix(new ColorMatrix(colorMatrixElements));
|
||||
}
|
||||
|
||||
g.DrawImage(image,
|
||||
r, // destination rectangle
|
||||
0, 0, image.Size.Width, image.Size.Height, // source rectangle
|
||||
GraphicsUnit.Pixel,
|
||||
imageAttributes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An adornment that will draw text
|
||||
/// </summary>
|
||||
public class TextAdornment : GraphicAdornment
|
||||
{
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the text
|
||||
/// Set this to Color.Empty to not draw a background
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The background color of the text"),
|
||||
DefaultValue(typeof(Color), "")]
|
||||
public Color BackColor {
|
||||
get { return this.backColor; }
|
||||
set { this.backColor = value; }
|
||||
}
|
||||
private Color backColor = Color.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brush that will be used to paint the text
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public Brush BackgroundBrush {
|
||||
get {
|
||||
return new SolidBrush(Color.FromArgb(this.workingTransparency, this.BackColor));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the border around the billboard.
|
||||
/// Set this to Color.Empty to remove the border
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The color of the border around the text"),
|
||||
DefaultValue(typeof(Color), "")]
|
||||
public Color BorderColor {
|
||||
get { return this.borderColor; }
|
||||
set { this.borderColor = value; }
|
||||
}
|
||||
private Color borderColor = Color.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brush that will be used to paint the text
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public Pen BorderPen {
|
||||
get {
|
||||
return new Pen(Color.FromArgb(this.workingTransparency, this.BorderColor), this.BorderWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the border around the text
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The width of the border around the text"),
|
||||
DefaultValue(0.0f)]
|
||||
public float BorderWidth {
|
||||
get { return this.borderWidth; }
|
||||
set { this.borderWidth = value; }
|
||||
}
|
||||
private float borderWidth;
|
||||
|
||||
/// <summary>
|
||||
/// How rounded should the corners of the border be? 0 means no rounding.
|
||||
/// </summary>
|
||||
/// <remarks>If this value is too large, the edges of the border will appear odd.</remarks>
|
||||
[Category("ObjectListView"),
|
||||
Description("How rounded should the corners of the border be? 0 means no rounding."),
|
||||
DefaultValue(16.0f),
|
||||
NotifyParentProperty(true)]
|
||||
public float CornerRounding {
|
||||
get { return this.cornerRounding; }
|
||||
set { this.cornerRounding = value; }
|
||||
}
|
||||
private float cornerRounding = 16.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the font that will be used to draw the text
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The font that will be used to draw the text"),
|
||||
DefaultValue(null),
|
||||
NotifyParentProperty(true)]
|
||||
public Font Font {
|
||||
get { return this.font; }
|
||||
set { this.font = value; }
|
||||
}
|
||||
private Font font;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font that will be used to draw the text or a reasonable default
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public Font FontOrDefault {
|
||||
get {
|
||||
return this.Font ?? new Font("Tahoma", 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does this text have a background?
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool HasBackground {
|
||||
get {
|
||||
return this.BackColor != Color.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does this overlay have a border?
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool HasBorder {
|
||||
get {
|
||||
return this.BorderColor != Color.Empty && this.BorderWidth > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum width of the text. Text longer than this will wrap.
|
||||
/// 0 means no maximum.
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The maximum width the text (0 means no maximum). Text longer than this will wrap"),
|
||||
DefaultValue(0)]
|
||||
public int MaximumTextWidth {
|
||||
get { return this.maximumTextWidth; }
|
||||
set { this.maximumTextWidth = value; }
|
||||
}
|
||||
private int maximumTextWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the formatting that should be used on the text
|
||||
/// </summary>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public virtual StringFormat StringFormat {
|
||||
get {
|
||||
if (this.stringFormat == null) {
|
||||
this.stringFormat = new StringFormat();
|
||||
this.stringFormat.Alignment = StringAlignment.Center;
|
||||
this.stringFormat.LineAlignment = StringAlignment.Center;
|
||||
this.stringFormat.Trimming = StringTrimming.EllipsisCharacter;
|
||||
if (!this.Wrap)
|
||||
this.stringFormat.FormatFlags = StringFormatFlags.NoWrap;
|
||||
}
|
||||
return this.stringFormat;
|
||||
}
|
||||
set { this.stringFormat = value; }
|
||||
}
|
||||
private StringFormat stringFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text that will be drawn
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The text that will be drawn over the top of the ListView"),
|
||||
DefaultValue(null),
|
||||
NotifyParentProperty(true),
|
||||
Localizable(true)]
|
||||
public string Text {
|
||||
get { return this.text; }
|
||||
set { this.text = value; }
|
||||
}
|
||||
private string text;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brush that will be used to paint the text
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public Brush TextBrush {
|
||||
get {
|
||||
return new SolidBrush(Color.FromArgb(this.workingTransparency, this.TextColor));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the text
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The color of the text"),
|
||||
DefaultValue(typeof(Color), "DarkBlue"),
|
||||
NotifyParentProperty(true)]
|
||||
public Color TextColor {
|
||||
get { return this.textColor; }
|
||||
set { this.textColor = value; }
|
||||
}
|
||||
private Color textColor = Color.DarkBlue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the text will wrap when it exceeds its bounds
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("Will the text wrap?"),
|
||||
DefaultValue(true)]
|
||||
public bool Wrap {
|
||||
get { return this.wrap; }
|
||||
set { this.wrap = value; }
|
||||
}
|
||||
private bool wrap = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Draw our text with our stored configuration in relation to the given
|
||||
/// reference rectangle
|
||||
/// </summary>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The reference rectangle in relation to which the text will be drawn</param>
|
||||
public virtual void DrawText(Graphics g, Rectangle r) {
|
||||
this.DrawText(g, r, this.Text, this.Transparency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the given text with our stored configuration
|
||||
/// </summary>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The reference rectangle in relation to which the text will be drawn</param>
|
||||
/// <param name="s">The text to draw</param>
|
||||
/// <param name="transparency">How opaque should be text be</param>
|
||||
public virtual void DrawText(Graphics g, Rectangle r, string s, int transparency) {
|
||||
if (String.IsNullOrEmpty(s))
|
||||
return;
|
||||
|
||||
Rectangle textRect = this.CalculateTextBounds(g, r, s);
|
||||
this.DrawBorderedText(g, textRect, s, transparency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the text with a border
|
||||
/// </summary>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="textRect">The bounds within which the text should be drawn</param>
|
||||
/// <param name="text">The text to draw</param>
|
||||
/// <param name="transparency">How opaque should be text be</param>
|
||||
protected virtual void DrawBorderedText(Graphics g, Rectangle textRect, string text, int transparency) {
|
||||
Rectangle borderRect = textRect;
|
||||
borderRect.Inflate((int)this.BorderWidth / 2, (int)this.BorderWidth / 2);
|
||||
borderRect.Y -= 1; // Looker better a little higher
|
||||
|
||||
try {
|
||||
this.ApplyRotation(g, textRect);
|
||||
using (GraphicsPath path = this.GetRoundedRect(borderRect, this.CornerRounding)) {
|
||||
this.workingTransparency = transparency;
|
||||
if (this.HasBackground) {
|
||||
using (Brush b = this.BackgroundBrush)
|
||||
g.FillPath(b, path);
|
||||
}
|
||||
|
||||
using (Brush b = this.TextBrush)
|
||||
g.DrawString(text, this.FontOrDefault, b, textRect, this.StringFormat);
|
||||
|
||||
if (this.HasBorder) {
|
||||
using (Pen p = this.BorderPen)
|
||||
g.DrawPath(p, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.UnapplyRotation(g);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the rectangle that will be the precise bounds of the displayed text
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="s"></param>
|
||||
/// <returns>The bounds of the text</returns>
|
||||
protected virtual Rectangle CalculateTextBounds(Graphics g, Rectangle r, string s) {
|
||||
int maxWidth = this.MaximumTextWidth <= 0 ? r.Width : this.MaximumTextWidth;
|
||||
SizeF sizeF = g.MeasureString(s, this.FontOrDefault, maxWidth, this.StringFormat);
|
||||
Size size = new Size(1 + (int)sizeF.Width, 1 + (int)sizeF.Height);
|
||||
return this.CreateAlignedRectangle(r, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a GraphicPath that is a round cornered rectangle
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle</param>
|
||||
/// <param name="diameter">The diameter of the corners</param>
|
||||
/// <returns>A round cornered rectagle path</returns>
|
||||
/// <remarks>If I could rely on people using C# 3.0+, this should be
|
||||
/// an extension method of GraphicsPath.</remarks>
|
||||
protected virtual GraphicsPath GetRoundedRect(Rectangle rect, float diameter) {
|
||||
GraphicsPath path = new GraphicsPath();
|
||||
|
||||
if (diameter > 0) {
|
||||
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
|
||||
path.AddArc(arc, 180, 90);
|
||||
arc.X = rect.Right - diameter;
|
||||
path.AddArc(arc, 270, 90);
|
||||
arc.Y = rect.Bottom - diameter;
|
||||
path.AddArc(arc, 0, 90);
|
||||
arc.X = rect.Left;
|
||||
path.AddArc(arc, 90, 90);
|
||||
path.CloseFigure();
|
||||
} else {
|
||||
path.AddRectangle(rect);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private int workingTransparency;
|
||||
}
|
||||
}
|
||||
820
ObjectListView/Rendering/Decorations.cs
Normal file
@@ -0,0 +1,820 @@
|
||||
/*
|
||||
* Decorations - Images, text or other things that can be rendered onto an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 19/08/2009 10:56 PM
|
||||
*
|
||||
* Change log:
|
||||
* 2011-04-04 JPP - Added ability to have a gradient background on BorderDecoration
|
||||
* v2.4
|
||||
* 2010-04-15 JPP - Tweaked LightBoxDecoration a little
|
||||
* v2.3
|
||||
* 2009-09-23 JPP - Added LeftColumn and RightColumn to RowBorderDecoration
|
||||
* 2009-08-23 JPP - Added LightBoxDecoration
|
||||
* 2009-08-19 JPP - Initial version. Separated from Overlays.cs
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A decoration is an overlay that draws itself in relation to a given row or cell.
|
||||
/// Decorations scroll when the listview scrolls.
|
||||
/// </summary>
|
||||
public interface IDecoration : IOverlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the row that is to be decorated
|
||||
/// </summary>
|
||||
OLVListItem ListItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subitem that is to be decorated
|
||||
/// </summary>
|
||||
OLVListSubItem SubItem { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An AbstractDecoration is a safe do-nothing implementation of the IDecoration interface
|
||||
/// </summary>
|
||||
public class AbstractDecoration : IDecoration
|
||||
{
|
||||
#region IDecoration Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the row that is to be decorated
|
||||
/// </summary>
|
||||
public OLVListItem ListItem {
|
||||
get { return listItem; }
|
||||
set { listItem = value; }
|
||||
}
|
||||
private OLVListItem listItem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subitem that is to be decorated
|
||||
/// </summary>
|
||||
public OLVListSubItem SubItem {
|
||||
get { return subItem; }
|
||||
set { subItem = value; }
|
||||
}
|
||||
private OLVListSubItem subItem;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounds of the decorations row
|
||||
/// </summary>
|
||||
public Rectangle RowBounds {
|
||||
get {
|
||||
if (this.ListItem == null)
|
||||
return Rectangle.Empty;
|
||||
else
|
||||
return this.ListItem.Bounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the bounds of the decorations cell
|
||||
/// </summary>
|
||||
public Rectangle CellBounds {
|
||||
get {
|
||||
if (this.ListItem == null || this.SubItem == null)
|
||||
return Rectangle.Empty;
|
||||
else
|
||||
return this.ListItem.GetSubItemBounds(this.ListItem.SubItems.IndexOf(this.SubItem));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOverlay Members
|
||||
|
||||
/// <summary>
|
||||
/// Draw the decoration
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This decoration draws a slight tint over a column of the
|
||||
/// owning listview. If no column is explicitly set, the selected
|
||||
/// column in the listview will be used.
|
||||
/// The selected column is normally the sort column, but does not have to be.
|
||||
/// </summary>
|
||||
public class TintedColumnDecoration : AbstractDecoration
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a TintedColumnDecoration
|
||||
/// </summary>
|
||||
public TintedColumnDecoration() {
|
||||
this.Tint = Color.FromArgb(15, Color.Blue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TintedColumnDecoration
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
public TintedColumnDecoration(OLVColumn column)
|
||||
: this() {
|
||||
this.ColumnToTint = column;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column that will be tinted
|
||||
/// </summary>
|
||||
public OLVColumn ColumnToTint {
|
||||
get { return this.columnToTint; }
|
||||
set { this.columnToTint = value; }
|
||||
}
|
||||
private OLVColumn columnToTint;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color that will be 'tinted' over the selected column
|
||||
/// </summary>
|
||||
public Color Tint {
|
||||
get { return this.tint; }
|
||||
set {
|
||||
if (this.tint == value)
|
||||
return;
|
||||
|
||||
if (this.tintBrush != null) {
|
||||
this.tintBrush.Dispose();
|
||||
this.tintBrush = null;
|
||||
}
|
||||
|
||||
this.tint = value;
|
||||
this.tintBrush = new SolidBrush(this.tint);
|
||||
}
|
||||
}
|
||||
private Color tint;
|
||||
private SolidBrush tintBrush;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOverlay Members
|
||||
|
||||
/// <summary>
|
||||
/// Draw a slight colouring over our tinted column
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overlay only works when:
|
||||
/// - the list is in Details view
|
||||
/// - there is at least one row
|
||||
/// - there is a selected column (or a specified tint column)
|
||||
/// </remarks>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
|
||||
if (olv.View != System.Windows.Forms.View.Details)
|
||||
return;
|
||||
|
||||
if (olv.GetItemCount() == 0)
|
||||
return;
|
||||
|
||||
OLVColumn column = this.ColumnToTint ?? olv.SelectedColumn;
|
||||
if (column == null)
|
||||
return;
|
||||
|
||||
Point sides = NativeMethods.GetScrolledColumnSides(olv, column.Index);
|
||||
if (sides.X == -1)
|
||||
return;
|
||||
|
||||
Rectangle columnBounds = new Rectangle(sides.X, r.Top, sides.Y - sides.X, r.Bottom);
|
||||
|
||||
// Find the bottom of the last item. The tinting should extend only to there.
|
||||
OLVListItem lastItem = olv.GetLastItemInDisplayOrder();
|
||||
if (lastItem != null) {
|
||||
Rectangle lastItemBounds = lastItem.Bounds;
|
||||
if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom)
|
||||
columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top;
|
||||
}
|
||||
g.FillRectangle(this.tintBrush, columnBounds);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This decoration draws an optionally filled border around a rectangle.
|
||||
/// Subclasses must override CalculateBounds().
|
||||
/// </summary>
|
||||
public class BorderDecoration : AbstractDecoration
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a BorderDecoration
|
||||
/// </summary>
|
||||
public BorderDecoration()
|
||||
: this(new Pen(Color.FromArgb(64, Color.Blue), 1)) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a BorderDecoration
|
||||
/// </summary>
|
||||
/// <param name="borderPen">The pen used to draw the border</param>
|
||||
public BorderDecoration(Pen borderPen) {
|
||||
this.BorderPen = borderPen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a BorderDecoration
|
||||
/// </summary>
|
||||
/// <param name="borderPen">The pen used to draw the border</param>
|
||||
/// <param name="fill">The brush used to fill the rectangle</param>
|
||||
public BorderDecoration(Pen borderPen, Brush fill) {
|
||||
this.BorderPen = borderPen;
|
||||
this.FillBrush = fill;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pen that will be used to draw the border
|
||||
/// </summary>
|
||||
public Pen BorderPen {
|
||||
get { return this.borderPen; }
|
||||
set { this.borderPen = value; }
|
||||
}
|
||||
private Pen borderPen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding that will be added to the bounds of the item
|
||||
/// before drawing the border and fill.
|
||||
/// </summary>
|
||||
public Size BoundsPadding {
|
||||
get { return this.boundsPadding; }
|
||||
set { this.boundsPadding = value; }
|
||||
}
|
||||
private Size boundsPadding = new Size(-1, 2);
|
||||
|
||||
/// <summary>
|
||||
/// How rounded should the corners of the border be? 0 means no rounding.
|
||||
/// </summary>
|
||||
/// <remarks>If this value is too large, the edges of the border will appear odd.</remarks>
|
||||
public float CornerRounding {
|
||||
get { return this.cornerRounding; }
|
||||
set { this.cornerRounding = value; }
|
||||
}
|
||||
private float cornerRounding = 16.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush that will be used to fill the border
|
||||
/// </summary>
|
||||
/// <remarks>This value is ignored when using gradient brush</remarks>
|
||||
public Brush FillBrush {
|
||||
get { return this.fillBrush; }
|
||||
set { this.fillBrush = value; }
|
||||
}
|
||||
private Brush fillBrush = new SolidBrush(Color.FromArgb(64, Color.Blue));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color that will be used as the start of a gradient fill.
|
||||
/// </summary>
|
||||
/// <remarks>This and FillGradientTo must be given value to show a gradient</remarks>
|
||||
public Color? FillGradientFrom {
|
||||
get { return this.fillGradientFrom; }
|
||||
set { this.fillGradientFrom = value; }
|
||||
}
|
||||
private Color? fillGradientFrom;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color that will be used as the end of a gradient fill.
|
||||
/// </summary>
|
||||
/// <remarks>This and FillGradientFrom must be given value to show a gradient</remarks>
|
||||
public Color? FillGradientTo {
|
||||
get { return this.fillGradientTo; }
|
||||
set { this.fillGradientTo = value; }
|
||||
}
|
||||
private Color? fillGradientTo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the fill mode that will be used for the gradient.
|
||||
/// </summary>
|
||||
public LinearGradientMode FillGradientMode {
|
||||
get { return this.fillGradientMode; }
|
||||
set { this.fillGradientMode = value; }
|
||||
}
|
||||
private LinearGradientMode fillGradientMode = LinearGradientMode.Vertical;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOverlay Members
|
||||
|
||||
/// <summary>
|
||||
/// Draw a filled border
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
Rectangle bounds = this.CalculateBounds();
|
||||
if (!bounds.IsEmpty)
|
||||
this.DrawFilledBorder(g, bounds);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Subclass responsibility
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses should override this to say where the border should be drawn
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual Rectangle CalculateBounds() {
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation utlities
|
||||
|
||||
/// <summary>
|
||||
/// Do the actual work of drawing the filled border
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="bounds"></param>
|
||||
protected void DrawFilledBorder(Graphics g, Rectangle bounds) {
|
||||
bounds.Inflate(this.BoundsPadding);
|
||||
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
|
||||
if (this.FillGradientFrom != null && this.FillGradientTo != null) {
|
||||
if (this.FillBrush != null)
|
||||
this.FillBrush.Dispose();
|
||||
this.FillBrush = new LinearGradientBrush(bounds, this.FillGradientFrom.Value, this.FillGradientTo.Value, this.FillGradientMode);
|
||||
}
|
||||
if (this.FillBrush != null)
|
||||
g.FillPath(this.FillBrush, path);
|
||||
if (this.BorderPen != null)
|
||||
g.DrawPath(this.BorderPen, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a GraphicsPath that represents a round cornered rectangle.
|
||||
/// </summary>
|
||||
/// <param name="rect"></param>
|
||||
/// <param name="diameter">If this is 0 or less, the rectangle will not be rounded.</param>
|
||||
/// <returns></returns>
|
||||
protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) {
|
||||
GraphicsPath path = new GraphicsPath();
|
||||
|
||||
if (diameter <= 0.0f) {
|
||||
path.AddRectangle(rect);
|
||||
} else {
|
||||
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
|
||||
path.AddArc(arc, 180, 90);
|
||||
arc.X = rect.Right - diameter;
|
||||
path.AddArc(arc, 270, 90);
|
||||
arc.Y = rect.Bottom - diameter;
|
||||
path.AddArc(arc, 0, 90);
|
||||
arc.X = rect.Left;
|
||||
path.AddArc(arc, 90, 90);
|
||||
path.CloseFigure();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class draw a border around the decorated row
|
||||
/// </summary>
|
||||
public class RowBorderDecoration : BorderDecoration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the left most column to be used for the border
|
||||
/// </summary>
|
||||
public int LeftColumn {
|
||||
get { return leftColumn; }
|
||||
set { leftColumn = value; }
|
||||
}
|
||||
private int leftColumn = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the right most column to be used for the border
|
||||
/// </summary>
|
||||
public int RightColumn {
|
||||
get { return rightColumn; }
|
||||
set { rightColumn = value; }
|
||||
}
|
||||
private int rightColumn = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the boundaries of the border
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Rectangle CalculateBounds() {
|
||||
Rectangle bounds = this.RowBounds;
|
||||
if (this.ListItem == null)
|
||||
return bounds;
|
||||
|
||||
if (this.LeftColumn >= 0) {
|
||||
Rectangle leftCellBounds = this.ListItem.GetSubItemBounds(this.LeftColumn);
|
||||
if (!leftCellBounds.IsEmpty) {
|
||||
bounds.Width = bounds.Right - leftCellBounds.Left;
|
||||
bounds.X = leftCellBounds.Left;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.RightColumn >= 0) {
|
||||
Rectangle rightCellBounds = this.ListItem.GetSubItemBounds(this.RightColumn);
|
||||
if (!rightCellBounds.IsEmpty) {
|
||||
bounds.Width = rightCellBounds.Right - bounds.Left;
|
||||
}
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class draw a border around the decorated subitem.
|
||||
/// </summary>
|
||||
public class CellBorderDecoration : BorderDecoration
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculate the boundaries of the border
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Rectangle CalculateBounds() {
|
||||
return this.CellBounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This decoration puts a border around the cell being edited and
|
||||
/// optionally "lightboxes" the cell (makes the rest of the control dark).
|
||||
/// </summary>
|
||||
public class EditingCellBorderDecoration : BorderDecoration
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a EditingCellBorderDecoration
|
||||
/// </summary>
|
||||
public EditingCellBorderDecoration() {
|
||||
this.FillBrush = null;
|
||||
this.BorderPen = new Pen(Color.DarkBlue, 2);
|
||||
this.CornerRounding = 8;
|
||||
this.BoundsPadding = new Size(10, 8);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a EditingCellBorderDecoration
|
||||
/// </summary>
|
||||
/// <param name="useLightBox">Should the decoration use a lighbox display style?</param>
|
||||
public EditingCellBorderDecoration(bool useLightBox) : this()
|
||||
{
|
||||
this.UseLightbox = useLightbox;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configuration properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set whether the decoration should make the rest of
|
||||
/// the control dark when a cell is being edited
|
||||
/// </summary>
|
||||
/// <remarks>If this is true, FillBrush is used to overpaint
|
||||
/// the control.</remarks>
|
||||
public bool UseLightbox {
|
||||
get { return this.useLightbox; }
|
||||
set {
|
||||
if (this.useLightbox == value)
|
||||
return;
|
||||
this.useLightbox = value;
|
||||
if (this.useLightbox) {
|
||||
if (this.FillBrush == null)
|
||||
this.FillBrush = new SolidBrush(Color.FromArgb(64, Color.Black));
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool useLightbox;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Draw the decoration
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
if (!olv.IsCellEditing)
|
||||
return;
|
||||
|
||||
Rectangle bounds = olv.CellEditor.Bounds;
|
||||
if (bounds.IsEmpty)
|
||||
return;
|
||||
|
||||
bounds.Inflate(this.BoundsPadding);
|
||||
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
|
||||
if (this.FillBrush != null) {
|
||||
if (this.UseLightbox) {
|
||||
using (Region newClip = new Region(r)) {
|
||||
newClip.Exclude(path);
|
||||
Region originalClip = g.Clip;
|
||||
g.Clip = newClip;
|
||||
g.FillRectangle(this.FillBrush, r);
|
||||
g.Clip = originalClip;
|
||||
}
|
||||
} else {
|
||||
g.FillPath(this.FillBrush, path);
|
||||
}
|
||||
}
|
||||
if (this.BorderPen != null)
|
||||
g.DrawPath(this.BorderPen, path);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This decoration causes everything *except* the row under the mouse to be overpainted
|
||||
/// with a tint, making the row under the mouse stand out in comparison.
|
||||
/// The darker and more opaque the fill color, the more obvious the
|
||||
/// decorated row becomes.
|
||||
/// </summary>
|
||||
public class LightBoxDecoration : BorderDecoration
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a LightBoxDecoration
|
||||
/// </summary>
|
||||
public LightBoxDecoration() {
|
||||
this.BoundsPadding = new Size(-1, 4);
|
||||
this.CornerRounding = 8.0f;
|
||||
this.FillBrush = new SolidBrush(Color.FromArgb(72, Color.Black));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a tint over everything in the ObjectListView except the
|
||||
/// row under the mouse.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
if (!r.Contains(olv.PointToClient(Cursor.Position)))
|
||||
return;
|
||||
|
||||
Rectangle bounds = this.RowBounds;
|
||||
if (bounds.IsEmpty) {
|
||||
if (olv.View == View.Tile)
|
||||
g.FillRectangle(this.FillBrush, r);
|
||||
return;
|
||||
}
|
||||
|
||||
using (Region newClip = new Region(r)) {
|
||||
bounds.Inflate(this.BoundsPadding);
|
||||
newClip.Exclude(this.GetRoundedRect(bounds, this.CornerRounding));
|
||||
Region originalClip = g.Clip;
|
||||
g.Clip = newClip;
|
||||
g.FillRectangle(this.FillBrush, r);
|
||||
g.Clip = originalClip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class put an Image over the row/cell that it is decorating
|
||||
/// </summary>
|
||||
public class ImageDecoration : ImageAdornment, IDecoration
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create an image decoration
|
||||
/// </summary>
|
||||
public ImageDecoration() {
|
||||
this.Alignment = ContentAlignment.MiddleRight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an image decoration
|
||||
/// </summary>
|
||||
/// <param name="image"></param>
|
||||
public ImageDecoration(Image image)
|
||||
: this() {
|
||||
this.Image = image;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an image decoration
|
||||
/// </summary>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="transparency"></param>
|
||||
public ImageDecoration(Image image, int transparency)
|
||||
: this() {
|
||||
this.Image = image;
|
||||
this.Transparency = transparency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an image decoration
|
||||
/// </summary>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="alignment"></param>
|
||||
public ImageDecoration(Image image, ContentAlignment alignment)
|
||||
: this() {
|
||||
this.Image = image;
|
||||
this.Alignment = alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an image decoration
|
||||
/// </summary>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="transparency"></param>
|
||||
/// <param name="alignment"></param>
|
||||
public ImageDecoration(Image image, int transparency, ContentAlignment alignment)
|
||||
: this() {
|
||||
this.Image = image;
|
||||
this.Transparency = transparency;
|
||||
this.Alignment = alignment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDecoration Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item being decorated
|
||||
/// </summary>
|
||||
public OLVListItem ListItem {
|
||||
get { return listItem; }
|
||||
set { listItem = value; }
|
||||
}
|
||||
private OLVListItem listItem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sub item being decorated
|
||||
/// </summary>
|
||||
public OLVListSubItem SubItem {
|
||||
get { return subItem; }
|
||||
set { subItem = value; }
|
||||
}
|
||||
private OLVListSubItem subItem;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Draw this decoration
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView being decorated</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
this.DrawImage(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class draw some text over the row/cell that they are decorating
|
||||
/// </summary>
|
||||
public class TextDecoration : TextAdornment, IDecoration
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextDecoration
|
||||
/// </summary>
|
||||
public TextDecoration() {
|
||||
this.Alignment = ContentAlignment.MiddleRight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextDecoration
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public TextDecoration(string text)
|
||||
: this() {
|
||||
this.Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextDecoration
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="transparency"></param>
|
||||
public TextDecoration(string text, int transparency)
|
||||
: this() {
|
||||
this.Text = text;
|
||||
this.Transparency = transparency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextDecoration
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="alignment"></param>
|
||||
public TextDecoration(string text, ContentAlignment alignment)
|
||||
: this() {
|
||||
this.Text = text;
|
||||
this.Alignment = alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a TextDecoration
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="transparency"></param>
|
||||
/// <param name="alignment"></param>
|
||||
public TextDecoration(string text, int transparency, ContentAlignment alignment)
|
||||
: this() {
|
||||
this.Text = text;
|
||||
this.Transparency = transparency;
|
||||
this.Alignment = alignment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDecoration Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item being decorated
|
||||
/// </summary>
|
||||
public OLVListItem ListItem {
|
||||
get { return listItem; }
|
||||
set { listItem = value; }
|
||||
}
|
||||
private OLVListItem listItem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sub item being decorated
|
||||
/// </summary>
|
||||
public OLVListSubItem SubItem {
|
||||
get { return subItem; }
|
||||
set { subItem = value; }
|
||||
}
|
||||
private OLVListSubItem subItem;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Draw this decoration
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView being decorated</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
this.DrawText(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
302
ObjectListView/Rendering/Overlays.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Overlays - Images, text or other things that can be rendered over the top of a ListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 14/04/2009 4:36 PM
|
||||
*
|
||||
* Change log:
|
||||
* v2.3
|
||||
* 2009-08-17 JPP - Overlays now use Adornments
|
||||
* - Added ITransparentOverlay interface. Overlays can now have separate transparency levels
|
||||
* 2009-08-10 JPP - Moved decoration related code to new file
|
||||
* v2.2.1
|
||||
* 200-07-24 JPP - TintedColumnDecoration now works when last item is a member of a collapsed
|
||||
* group (well, it no longer crashes).
|
||||
* v2.2
|
||||
* 2009-06-01 JPP - Make sure that TintedColumnDecoration reaches to the last item in group view
|
||||
* 2009-05-05 JPP - Unified BillboardOverlay text rendering with that of TextOverlay
|
||||
* 2009-04-30 JPP - Added TintedColumnDecoration
|
||||
* 2009-04-14 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// The interface for an object which can draw itself over the top of
|
||||
/// an ObjectListView.
|
||||
/// </summary>
|
||||
public interface IOverlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Draw this overlay
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView that is being overlaid</param>
|
||||
/// <param name="g">The Graphics onto the given OLV</param>
|
||||
/// <param name="r">The content area of the OLV</param>
|
||||
void Draw(ObjectListView olv, Graphics g, Rectangle r);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface for an overlay that supports variable levels of transparency
|
||||
/// </summary>
|
||||
public interface ITransparentOverlay : IOverlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the transparency of the overlay.
|
||||
/// 0 is completely transparent, 255 is completely opaque.
|
||||
/// </summary>
|
||||
int Transparency { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A null implementation of the IOverlay interface
|
||||
/// </summary>
|
||||
public class AbstractOverlay : ITransparentOverlay
|
||||
{
|
||||
#region IOverlay Members
|
||||
|
||||
/// <summary>
|
||||
/// Draw this overlay
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView that is being overlaid</param>
|
||||
/// <param name="g">The Graphics onto the given OLV</param>
|
||||
/// <param name="r">The content area of the OLV</param>
|
||||
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITransparentOverlay Members
|
||||
|
||||
/// <summary>
|
||||
/// How transparent should this overlay be?
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("How transparent should this overlay be"),
|
||||
DefaultValue(128),
|
||||
NotifyParentProperty(true)]
|
||||
public int Transparency {
|
||||
get { return this.transparency; }
|
||||
set { this.transparency = Math.Min(255, Math.Max(0, value)); }
|
||||
}
|
||||
private int transparency = 128;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overlay that will draw an image over the top of the ObjectListView
|
||||
/// </summary>
|
||||
[TypeConverter("BrightIdeasSoftware.Design.OverlayConverter")]
|
||||
public class ImageOverlay : ImageAdornment, ITransparentOverlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an ImageOverlay
|
||||
/// </summary>
|
||||
public ImageOverlay() {
|
||||
this.Alignment = System.Drawing.ContentAlignment.BottomRight;
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the horizontal inset by which the position of the overlay will be adjusted
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The horizontal inset by which the position of the overlay will be adjusted"),
|
||||
DefaultValue(20),
|
||||
NotifyParentProperty(true)]
|
||||
public int InsetX {
|
||||
get { return this.insetX; }
|
||||
set { this.insetX = Math.Max(0, value); }
|
||||
}
|
||||
private int insetX = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertical inset by which the position of the overlay will be adjusted
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("Gets or sets the vertical inset by which the position of the overlay will be adjusted"),
|
||||
DefaultValue(20),
|
||||
NotifyParentProperty(true)]
|
||||
public int InsetY {
|
||||
get { return this.insetY; }
|
||||
set { this.insetY = Math.Max(0, value); }
|
||||
}
|
||||
private int insetY = 20;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Draw this overlay
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView being decorated</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
Rectangle insetRect = r;
|
||||
insetRect.Inflate(-this.InsetX, -this.InsetY);
|
||||
|
||||
// We hard code a transparency of 255 here since transparency is handled by the glass panel
|
||||
this.DrawImage(g, insetRect, this.Image, 255);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overlay that will draw text over the top of the ObjectListView
|
||||
/// </summary>
|
||||
[TypeConverter("BrightIdeasSoftware.Design.OverlayConverter")]
|
||||
public class TextOverlay : TextAdornment, ITransparentOverlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a TextOverlay
|
||||
/// </summary>
|
||||
public TextOverlay() {
|
||||
this.Alignment = System.Drawing.ContentAlignment.BottomRight;
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the horizontal inset by which the position of the overlay will be adjusted
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("The horizontal inset by which the position of the overlay will be adjusted"),
|
||||
DefaultValue(20),
|
||||
NotifyParentProperty(true)]
|
||||
public int InsetX {
|
||||
get { return this.insetX; }
|
||||
set { this.insetX = Math.Max(0, value); }
|
||||
}
|
||||
private int insetX = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertical inset by which the position of the overlay will be adjusted
|
||||
/// </summary>
|
||||
[Category("ObjectListView"),
|
||||
Description("Gets or sets the vertical inset by which the position of the overlay will be adjusted"),
|
||||
DefaultValue(20),
|
||||
NotifyParentProperty(true)]
|
||||
public int InsetY {
|
||||
get { return this.insetY; }
|
||||
set { this.insetY = Math.Max(0, value); }
|
||||
}
|
||||
private int insetY = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the border will be drawn with rounded corners
|
||||
/// </summary>
|
||||
[Browsable(false),
|
||||
Obsolete("Use CornerRounding instead", false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool RoundCorneredBorder {
|
||||
get { return this.CornerRounding > 0; }
|
||||
set {
|
||||
if (value)
|
||||
this.CornerRounding = 16.0f;
|
||||
else
|
||||
this.CornerRounding = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Draw this overlay
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView being decorated</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
if (String.IsNullOrEmpty(this.Text))
|
||||
return;
|
||||
|
||||
Rectangle insetRect = r;
|
||||
insetRect.Inflate(-this.InsetX, -this.InsetY);
|
||||
// We hard code a transparency of 255 here since transparency is handled by the glass panel
|
||||
this.DrawText(g, insetRect, this.Text, 255);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Billboard overlay is a TextOverlay positioned at an absolute point
|
||||
/// </summary>
|
||||
public class BillboardOverlay : TextOverlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a BillboardOverlay
|
||||
/// </summary>
|
||||
public BillboardOverlay() {
|
||||
this.Transparency = 255;
|
||||
this.BackColor = Color.PeachPuff;
|
||||
this.TextColor = Color.Black;
|
||||
this.BorderColor = Color.Empty;
|
||||
this.Font = new Font("Tahoma", 10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets where should the top left of the billboard be placed
|
||||
/// </summary>
|
||||
public Point Location {
|
||||
get { return this.location; }
|
||||
set { this.location = value; }
|
||||
}
|
||||
private Point location;
|
||||
|
||||
/// <summary>
|
||||
/// Draw this overlay
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView being decorated</param>
|
||||
/// <param name="g">The Graphics used for drawing</param>
|
||||
/// <param name="r">The bounds of the rendering</param>
|
||||
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
|
||||
if (String.IsNullOrEmpty(this.Text))
|
||||
return;
|
||||
|
||||
// Calculate the bounds of the text, and then move it to where it should be
|
||||
Rectangle textRect = this.CalculateTextBounds(g, r, this.Text);
|
||||
textRect.Location = this.Location;
|
||||
|
||||
// Make sure the billboard is within the bounds of the List, as far as is possible
|
||||
if (textRect.Right > r.Width)
|
||||
textRect.X = Math.Max(r.Left, r.Width - textRect.Width);
|
||||
if (textRect.Bottom > r.Height)
|
||||
textRect.Y = Math.Max(r.Top, r.Height - textRect.Height);
|
||||
|
||||
this.DrawBorderedText(g, textRect, this.Text, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
3832
ObjectListView/Rendering/Renderers.cs
Normal file
400
ObjectListView/Rendering/Styles.cs
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Styles - A style is a group of formatting attributes that can be applied to a row or a cell
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 29/07/2009 23:09
|
||||
*
|
||||
* Change log:
|
||||
* v2.4
|
||||
* 2010-03-23 JPP - Added HeaderFormatStyle and support
|
||||
* v2.3
|
||||
* 2009-08-15 JPP - Added Decoration and Overlay properties to HotItemStyle
|
||||
* 2009-07-29 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
* - These should be more generally available. It should be possible to do something like this:
|
||||
* this.olv.GetItem(i).Style = new ItemStyle();
|
||||
* this.olv.GetItem(i).GetSubItem(j).Style = new CellStyle();
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// The common interface supported by all style objects
|
||||
/// </summary>
|
||||
public interface IItemStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or set the font that will be used by this style
|
||||
/// </summary>
|
||||
Font Font { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the font style
|
||||
/// </summary>
|
||||
FontStyle FontStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ForeColor
|
||||
/// </summary>
|
||||
Color ForeColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the BackColor
|
||||
/// </summary>
|
||||
Color BackColor { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic implementation of IItemStyle
|
||||
/// </summary>
|
||||
public class SimpleItemStyle : System.ComponentModel.Component, IItemStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the font that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(null)]
|
||||
public Font Font
|
||||
{
|
||||
get { return this.font; }
|
||||
set { this.font = value; }
|
||||
}
|
||||
|
||||
private Font font;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style of font that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(FontStyle.Regular)]
|
||||
public FontStyle FontStyle
|
||||
{
|
||||
get { return this.fontStyle; }
|
||||
set { this.fontStyle = value; }
|
||||
}
|
||||
|
||||
private FontStyle fontStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the text that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(typeof (Color), "")]
|
||||
public Color ForeColor
|
||||
{
|
||||
get { return this.foreColor; }
|
||||
set { this.foreColor = value; }
|
||||
}
|
||||
|
||||
private Color foreColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(typeof (Color), "")]
|
||||
public Color BackColor
|
||||
{
|
||||
get { return this.backColor; }
|
||||
set { this.backColor = value; }
|
||||
}
|
||||
|
||||
private Color backColor;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class specify how should "hot items" (non-selected
|
||||
/// rows under the cursor) be renderered.
|
||||
/// </summary>
|
||||
public class HotItemStyle : SimpleItemStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the overlay that should be drawn as part of the hot item
|
||||
/// </summary>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public IOverlay Overlay {
|
||||
get { return this.overlay; }
|
||||
set { this.overlay = value; }
|
||||
}
|
||||
private IOverlay overlay;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the decoration that should be drawn as part of the hot item
|
||||
/// </summary>
|
||||
/// <remarks>A decoration is different from an overlay in that an decoration
|
||||
/// scrolls with the listview contents, whilst an overlay does not.</remarks>
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public IDecoration Decoration {
|
||||
get { return this.decoration; }
|
||||
set { this.decoration = value; }
|
||||
}
|
||||
private IDecoration decoration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class defines how a cell should be formatted
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public class CellStyle : IItemStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the font that will be applied by this style
|
||||
/// </summary>
|
||||
public Font Font {
|
||||
get { return this.font; }
|
||||
set { this.font = value; }
|
||||
}
|
||||
private Font font;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style of font that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(FontStyle.Regular)]
|
||||
public FontStyle FontStyle {
|
||||
get { return this.fontStyle; }
|
||||
set { this.fontStyle = value; }
|
||||
}
|
||||
private FontStyle fontStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the text that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Color), "")]
|
||||
public Color ForeColor {
|
||||
get { return this.foreColor; }
|
||||
set { this.foreColor = value; }
|
||||
}
|
||||
private Color foreColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Color), "")]
|
||||
public Color BackColor {
|
||||
get { return this.backColor; }
|
||||
set { this.backColor = value; }
|
||||
}
|
||||
private Color backColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class describe how hyperlinks will appear
|
||||
/// </summary>
|
||||
public class HyperlinkStyle : System.ComponentModel.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a HyperlinkStyle
|
||||
/// </summary>
|
||||
public HyperlinkStyle() {
|
||||
this.Normal = new CellStyle();
|
||||
this.Normal.ForeColor = Color.Blue;
|
||||
this.Over = new CellStyle();
|
||||
this.Over.FontStyle = FontStyle.Underline;
|
||||
this.Visited = new CellStyle();
|
||||
this.Visited.ForeColor = Color.Purple;
|
||||
this.OverCursor = Cursors.Hand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What sort of formatting should be applied to hyperlinks in their normal state?
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("How should hyperlinks be drawn")]
|
||||
public CellStyle Normal {
|
||||
get { return this.normalStyle; }
|
||||
set { this.normalStyle = value; }
|
||||
}
|
||||
private CellStyle normalStyle;
|
||||
|
||||
/// <summary>
|
||||
/// What sort of formatting should be applied to hyperlinks when the mouse is over them?
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("How should hyperlinks be drawn when the mouse is over them?")]
|
||||
public CellStyle Over {
|
||||
get { return this.overStyle; }
|
||||
set { this.overStyle = value; }
|
||||
}
|
||||
private CellStyle overStyle;
|
||||
|
||||
/// <summary>
|
||||
/// What sort of formatting should be applied to hyperlinks after they have been clicked?
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("How should hyperlinks be drawn after they have been clicked")]
|
||||
public CellStyle Visited {
|
||||
get { return this.visitedStyle; }
|
||||
set { this.visitedStyle = value; }
|
||||
}
|
||||
private CellStyle visitedStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cursor that should be shown when the mouse is over a hyperlink.
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("What cursor should be shown when the mouse is over a link?")]
|
||||
public Cursor OverCursor {
|
||||
get { return this.overCursor; }
|
||||
set { this.overCursor = value; }
|
||||
}
|
||||
private Cursor overCursor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class control one the styling of one particular state
|
||||
/// (normal, hot, pressed) of a header control
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public class HeaderStateStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the font that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(null)]
|
||||
public Font Font {
|
||||
get { return this.font; }
|
||||
set { this.font = value; }
|
||||
}
|
||||
private Font font;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the text that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Color), "")]
|
||||
public Color ForeColor {
|
||||
get { return this.foreColor; }
|
||||
set { this.foreColor = value; }
|
||||
}
|
||||
private Color foreColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color that will be applied by this style
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Color), "")]
|
||||
public Color BackColor {
|
||||
get { return this.backColor; }
|
||||
set { this.backColor = value; }
|
||||
}
|
||||
private Color backColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color in which a frame will be drawn around the header for this column
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Color), "")]
|
||||
public Color FrameColor {
|
||||
get { return this.frameColor; }
|
||||
set { this.frameColor = value; }
|
||||
}
|
||||
private Color frameColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the frame that will be drawn around the header for this column
|
||||
/// </summary>
|
||||
[DefaultValue(0.0f)]
|
||||
public float FrameWidth {
|
||||
get { return this.frameWidth; }
|
||||
set { this.frameWidth = value; }
|
||||
}
|
||||
private float frameWidth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class defines how a header should be formatted in its various states.
|
||||
/// </summary>
|
||||
public class HeaderFormatStyle : System.ComponentModel.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new HeaderFormatStyle
|
||||
/// </summary>
|
||||
public HeaderFormatStyle() {
|
||||
this.Hot = new HeaderStateStyle();
|
||||
this.Normal = new HeaderStateStyle();
|
||||
this.Pressed = new HeaderStateStyle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What sort of formatting should be applied to a column header when the mouse is over it?
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("How should the header be drawn when the mouse is over it?")]
|
||||
public HeaderStateStyle Hot {
|
||||
get { return this.hotStyle; }
|
||||
set { this.hotStyle = value; }
|
||||
}
|
||||
private HeaderStateStyle hotStyle;
|
||||
|
||||
/// <summary>
|
||||
/// What sort of formatting should be applied to a column header in its normal state?
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("How should a column header normally be drawn")]
|
||||
public HeaderStateStyle Normal {
|
||||
get { return this.normalStyle; }
|
||||
set { this.normalStyle = value; }
|
||||
}
|
||||
private HeaderStateStyle normalStyle;
|
||||
|
||||
/// <summary>
|
||||
/// What sort of formatting should be applied to a column header when pressed?
|
||||
/// </summary>
|
||||
[Category("Appearance"),
|
||||
Description("How should a column header be drawn when it is pressed")]
|
||||
public HeaderStateStyle Pressed {
|
||||
get { return this.pressedStyle; }
|
||||
set { this.pressedStyle = value; }
|
||||
}
|
||||
private HeaderStateStyle pressedStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Set the font for all three states
|
||||
/// </summary>
|
||||
/// <param name="font"></param>
|
||||
public void SetFont(Font font) {
|
||||
this.Normal.Font = font;
|
||||
this.Hot.Font = font;
|
||||
this.Pressed.Font = font;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the fore color for all three states
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
public void SetForeColor(Color color) {
|
||||
this.Normal.ForeColor = color;
|
||||
this.Hot.ForeColor = color;
|
||||
this.Pressed.ForeColor = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the back color for all three states
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
public void SetBackColor(Color color) {
|
||||
this.Normal.BackColor = color;
|
||||
this.Hot.BackColor = color;
|
||||
this.Pressed.BackColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
309
ObjectListView/Rendering/TreeRenderer.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* TreeRenderer - Draw the major column in a TreeListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 27/06/2015
|
||||
*
|
||||
* Change log:
|
||||
* 2016-07-17 JPP - Added TreeRenderer.UseTriangles and IsShowGlyphs
|
||||
* 2015-06-27 JPP - Split out from TreeListView.cs
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Forms.VisualStyles;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
public partial class TreeListView {
|
||||
/// <summary>
|
||||
/// This class handles drawing the tree structure of the primary column.
|
||||
/// </summary>
|
||||
public class TreeRenderer : HighlightTextRenderer {
|
||||
/// <summary>
|
||||
/// Create a TreeRenderer
|
||||
/// </summary>
|
||||
public TreeRenderer() {
|
||||
this.LinePen = new Pen(Color.Blue, 1.0f);
|
||||
this.LinePen.DashStyle = DashStyle.Dot;
|
||||
}
|
||||
|
||||
#region Configuration properties
|
||||
|
||||
/// <summary>
|
||||
/// Should the renderer draw glyphs at the expansion points?
|
||||
/// </summary>
|
||||
/// <remarks>The expansion points will still function to expand/collapse even if this is false.</remarks>
|
||||
public bool IsShowGlyphs
|
||||
{
|
||||
get { return isShowGlyphs; }
|
||||
set { isShowGlyphs = value; }
|
||||
}
|
||||
private bool isShowGlyphs = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the renderer draw lines connecting siblings?
|
||||
/// </summary>
|
||||
public bool IsShowLines
|
||||
{
|
||||
get { return isShowLines; }
|
||||
set { isShowLines = value; }
|
||||
}
|
||||
private bool isShowLines = true;
|
||||
|
||||
/// <summary>
|
||||
/// Return the pen that will be used to draw the lines between branches
|
||||
/// </summary>
|
||||
public Pen LinePen
|
||||
{
|
||||
get { return linePen; }
|
||||
set { linePen = value; }
|
||||
}
|
||||
private Pen linePen;
|
||||
|
||||
/// <summary>
|
||||
/// Should the renderer draw triangles as the expansion glyphs?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This looks best with ShowLines = false
|
||||
/// </remarks>
|
||||
public bool UseTriangles
|
||||
{
|
||||
get { return useTriangles; }
|
||||
set { useTriangles = value; }
|
||||
}
|
||||
private bool useTriangles = false;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Return the branch that the renderer is currently drawing.
|
||||
/// </summary>
|
||||
private Branch Branch {
|
||||
get {
|
||||
return this.TreeListView.TreeModel.GetBranch(this.RowObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the TreeListView for which the renderer is being used.
|
||||
/// </summary>
|
||||
public TreeListView TreeListView {
|
||||
get {
|
||||
return (TreeListView)this.ListView;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many pixels will be reserved for each level of indentation?
|
||||
/// </summary>
|
||||
public static int PIXELS_PER_LEVEL = 16 + 1;
|
||||
|
||||
/// <summary>
|
||||
/// The real work of drawing the tree is done in this method
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
public override void Render(System.Drawing.Graphics g, System.Drawing.Rectangle r) {
|
||||
this.DrawBackground(g, r);
|
||||
|
||||
Branch br = this.Branch;
|
||||
|
||||
Rectangle paddedRectangle = this.ApplyCellPadding(r);
|
||||
|
||||
Rectangle expandGlyphRectangle = paddedRectangle;
|
||||
expandGlyphRectangle.Offset((br.Level - 1) * PIXELS_PER_LEVEL, 0);
|
||||
expandGlyphRectangle.Width = PIXELS_PER_LEVEL;
|
||||
expandGlyphRectangle.Height = PIXELS_PER_LEVEL;
|
||||
expandGlyphRectangle.Y = this.AlignVertically(paddedRectangle, expandGlyphRectangle);
|
||||
int expandGlyphRectangleMidVertical = expandGlyphRectangle.Y + (expandGlyphRectangle.Height/2);
|
||||
|
||||
if (this.IsShowLines)
|
||||
this.DrawLines(g, r, this.LinePen, br, expandGlyphRectangleMidVertical);
|
||||
|
||||
if (br.CanExpand && this.IsShowGlyphs)
|
||||
this.DrawExpansionGlyph(g, expandGlyphRectangle, br.IsExpanded);
|
||||
|
||||
int indent = br.Level * PIXELS_PER_LEVEL;
|
||||
paddedRectangle.Offset(indent, 0);
|
||||
paddedRectangle.Width -= indent;
|
||||
|
||||
this.DrawImageAndText(g, paddedRectangle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the expansion indicator
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="isExpanded"></param>
|
||||
protected virtual void DrawExpansionGlyph(Graphics g, Rectangle r, bool isExpanded) {
|
||||
if (this.UseStyles) {
|
||||
this.DrawExpansionGlyphStyled(g, r, isExpanded);
|
||||
} else {
|
||||
this.DrawExpansionGlyphManual(g, r, isExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not we should render using styles
|
||||
/// </summary>
|
||||
protected virtual bool UseStyles {
|
||||
get {
|
||||
return !this.IsPrinting && Application.RenderWithVisualStyles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the expansion indicator using styles
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="isExpanded"></param>
|
||||
protected virtual void DrawExpansionGlyphStyled(Graphics g, Rectangle r, bool isExpanded) {
|
||||
if (this.UseTriangles && this.IsShowLines) {
|
||||
using (SolidBrush b = new SolidBrush(GetBackgroundColor())) {
|
||||
Rectangle r2 = r;
|
||||
r2.Inflate(-2, -2);
|
||||
g.FillRectangle(b, r2);
|
||||
}
|
||||
}
|
||||
|
||||
VisualStyleRenderer renderer = new VisualStyleRenderer(DecideVisualElement(isExpanded));
|
||||
renderer.DrawBackground(g, r);
|
||||
}
|
||||
|
||||
private VisualStyleElement DecideVisualElement(bool isExpanded) {
|
||||
string klass = this.UseTriangles ? "Explorer::TreeView" : "TREEVIEW";
|
||||
int part = this.UseTriangles && this.IsExpansionHot ? 4 : 2;
|
||||
int state = isExpanded ? 2 : 1;
|
||||
return VisualStyleElement.CreateElement(klass, part, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the mouse over a checkbox in this cell?
|
||||
/// </summary>
|
||||
protected bool IsExpansionHot {
|
||||
get { return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.ExpandButton; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the expansion indicator without using styles
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="isExpanded"></param>
|
||||
protected virtual void DrawExpansionGlyphManual(Graphics g, Rectangle r, bool isExpanded) {
|
||||
int h = 8;
|
||||
int w = 8;
|
||||
int x = r.X + 4;
|
||||
int y = r.Y + (r.Height / 2) - 4;
|
||||
|
||||
g.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
|
||||
g.FillRectangle(Brushes.White, x + 1, y + 1, w - 1, h - 1);
|
||||
g.DrawLine(Pens.Black, x + 2, y + 4, x + w - 2, y + 4);
|
||||
|
||||
if (!isExpanded)
|
||||
g.DrawLine(Pens.Black, x + 4, y + 2, x + 4, y + h - 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the lines of the tree
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="p"></param>
|
||||
/// <param name="br"></param>
|
||||
/// <param name="glyphMidVertical"> </param>
|
||||
protected virtual void DrawLines(Graphics g, Rectangle r, Pen p, Branch br, int glyphMidVertical) {
|
||||
Rectangle r2 = r;
|
||||
r2.Width = PIXELS_PER_LEVEL;
|
||||
|
||||
// Vertical lines have to start on even points, otherwise the dotted line looks wrong.
|
||||
// This is only needed if pen is dotted.
|
||||
int top = r2.Top;
|
||||
//if (p.DashStyle == DashStyle.Dot && (top & 1) == 0)
|
||||
// top += 1;
|
||||
|
||||
// Draw lines for ancestors
|
||||
int midX;
|
||||
IList<Branch> ancestors = br.Ancestors;
|
||||
foreach (Branch ancestor in ancestors) {
|
||||
if (!ancestor.IsLastChild && !ancestor.IsOnlyBranch) {
|
||||
midX = r2.Left + r2.Width / 2;
|
||||
g.DrawLine(p, midX, top, midX, r2.Bottom);
|
||||
}
|
||||
r2.Offset(PIXELS_PER_LEVEL, 0);
|
||||
}
|
||||
|
||||
// Draw lines for this branch
|
||||
midX = r2.Left + r2.Width / 2;
|
||||
|
||||
// Horizontal line first
|
||||
g.DrawLine(p, midX, glyphMidVertical, r2.Right, glyphMidVertical);
|
||||
|
||||
// Vertical line second
|
||||
if (br.IsFirstBranch) {
|
||||
if (!br.IsLastChild && !br.IsOnlyBranch)
|
||||
g.DrawLine(p, midX, glyphMidVertical, midX, r2.Bottom);
|
||||
} else {
|
||||
if (br.IsLastChild)
|
||||
g.DrawLine(p, midX, top, midX, glyphMidVertical);
|
||||
else
|
||||
g.DrawLine(p, midX, top, midX, r2.Bottom);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do the hit test
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="hti"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) {
|
||||
Branch br = this.Branch;
|
||||
|
||||
Rectangle r = this.ApplyCellPadding(this.Bounds);
|
||||
if (br.CanExpand) {
|
||||
r.Offset((br.Level - 1) * PIXELS_PER_LEVEL, 0);
|
||||
r.Width = PIXELS_PER_LEVEL;
|
||||
if (r.Contains(x, y)) {
|
||||
hti.HitTestLocation = HitTestLocation.ExpandButton;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
r = this.Bounds;
|
||||
int indent = br.Level * PIXELS_PER_LEVEL;
|
||||
r.X += indent;
|
||||
r.Width -= indent;
|
||||
|
||||
// Ignore events in the indent zone
|
||||
if (x < r.Left) {
|
||||
hti.HitTestLocation = HitTestLocation.Nothing;
|
||||
} else {
|
||||
this.StandardHitTest(g, hti, r, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the edit rect
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="cellBounds"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="subItemIndex"></param>
|
||||
/// <param name="preferredSize"> </param>
|
||||
/// <returns></returns>
|
||||
protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
|
||||
return this.StandardGetEditRectangle(g, cellBounds, preferredSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
ObjectListView/Resources/clear-filter.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/coffee.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
ObjectListView/Resources/filter-icons3.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/filter.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/sort-ascending.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |