mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-09 12:17:13 +00:00
Compare commits
1512
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59687b6e2 | ||
|
|
c2bd9f26a7 | ||
|
|
f33ba954f5 | ||
|
|
150e44dcd5 | ||
|
|
511ce4b07d | ||
|
|
aea3fb5f07 | ||
|
|
5932a07eb2 | ||
|
|
2b07c4b662 | ||
|
|
bc689b91dc | ||
|
|
c6a42781e7 | ||
|
|
c92aedf433 | ||
|
|
7857f92f19 | ||
|
|
9361e5d401 | ||
|
|
ad1862a253 | ||
|
|
4af137894a | ||
|
|
55aca1fde9 | ||
|
|
53e1e936d9 | ||
|
|
ff1448fb54 | ||
|
|
95fb19f311 | ||
|
|
46d869a62f | ||
|
|
03140dbf64 | ||
|
|
052c96e05a | ||
|
|
9a88f9c324 | ||
|
|
747ea627c9 | ||
|
|
89c0ab405a | ||
|
|
04c2b98ae4 | ||
|
|
24b36cdd95 | ||
|
|
3e83bd2363 | ||
|
|
5c91a08eaf | ||
|
|
edfba15d6f | ||
|
|
48e786e8b5 | ||
|
|
1282f1ac0e | ||
|
|
ee2fb61fdd | ||
|
|
68a113e7ee | ||
|
|
3cf3b76446 | ||
|
|
d7d561d3a9 | ||
|
|
73eeda3c09 | ||
|
|
3d514b5010 | ||
|
|
93fc8d29cc | ||
|
|
145bf65542 | ||
|
|
f21555bb6c | ||
|
|
be072a283f | ||
|
|
9e8d51c94d | ||
|
|
b3051a5bd0 | ||
|
|
a523cfc381 | ||
|
|
6544aff88e | ||
|
|
7c1fdbc720 | ||
|
|
bd84804e32 | ||
|
|
1f2a257bab | ||
|
|
b0684929e2 | ||
|
|
22dc587a8f | ||
|
|
622fcf740e | ||
|
|
77b59c8a18 | ||
|
|
462a18930b | ||
|
|
fb07def5fa | ||
|
|
a03ee6726e | ||
|
|
1ef49ef290 | ||
|
|
76d52e07d4 | ||
|
|
044b0bb0d2 | ||
|
|
472b59ef80 | ||
|
|
6b559ce283 | ||
|
|
c17b806fd5 | ||
|
|
1dcea8fe4c | ||
|
|
d72a52b9ce | ||
|
|
61e01b08e5 | ||
|
|
37756a5853 | ||
|
|
b66690e304 | ||
|
|
ccc6007bab | ||
|
|
3179d56879 | ||
|
|
b631032857 | ||
|
|
49574d1c54 | ||
|
|
b7d7368ce6 | ||
|
|
e221156c11 | ||
|
|
8e346acdce | ||
|
|
7c36fa3f56 | ||
|
|
642e254522 | ||
|
|
72aa1b8416 | ||
|
|
edb1ef7f98 | ||
|
|
92beaba1d6 | ||
|
|
3b61b1c85d | ||
|
|
a529e752b3 | ||
|
|
d9791673df | ||
|
|
a49a20e2c2 | ||
|
|
8570476cff | ||
|
|
d0923efa1a | ||
|
|
eab8fe2d64 | ||
|
|
c9e8b2970b | ||
|
|
1440c156b2 | ||
|
|
ddfa4445ae | ||
|
|
1f501e6429 | ||
|
|
7969f118de | ||
|
|
706166dc5c | ||
|
|
4a8667e0f3 | ||
|
|
094fbcc67c | ||
|
|
d34c47c167 | ||
|
|
7226330514 | ||
|
|
852b2bd5d9 | ||
|
|
3d8ecfad47 | ||
|
|
9e6f1e0dc3 | ||
|
|
4deb7a3f51 | ||
|
|
5b0b030435 | ||
|
|
4edec2df2e | ||
|
|
e0596bdf75 | ||
|
|
6430463b5d | ||
|
|
d9e27e549f | ||
|
|
9f14dac5d2 | ||
|
|
d70199ca58 | ||
|
|
6b1cdbd19c | ||
|
|
5b99757a0d | ||
|
|
6c49116c68 | ||
|
|
68f0cf27df | ||
|
|
89757e35e6 | ||
|
|
24bf09b70c | ||
|
|
0ed2709124 | ||
|
|
a835202db1 | ||
|
|
830fbbab26 | ||
|
|
119e11cfe2 | ||
|
|
511e18b44f | ||
|
|
2f00d93b22 | ||
|
|
973fa1b978 | ||
|
|
79f40d24ae | ||
|
|
fdff92e5c8 | ||
|
|
653dd07162 | ||
|
|
b7968ca4a7 | ||
|
|
2a0a6449e5 | ||
|
|
b7c04d7d16 | ||
|
|
ab4c87e69f | ||
|
|
8257632706 | ||
|
|
3f8821b43b | ||
|
|
f6b8bc0c45 | ||
|
|
0618fa9bb2 | ||
|
|
2e5a483dce | ||
|
|
e608e0dbf3 | ||
|
|
a88e316a94 | ||
|
|
51ffa256ca | ||
|
|
52bd654850 | ||
|
|
0518d479d3 | ||
|
|
a893f3f5c1 | ||
|
|
9c2f37b43f | ||
|
|
0289ba17f1 | ||
|
|
5db94e1e52 | ||
|
|
a4bc08c29e | ||
|
|
b638d8e904 | ||
|
|
251ce7fdad | ||
|
|
a2337a2969 | ||
|
|
0c803e26ca | ||
|
|
ab25fadb1e | ||
|
|
e697f4eca7 | ||
|
|
80158a8982 | ||
|
|
55bcfacf3b | ||
|
|
52b7a82b4d | ||
|
|
471e605871 | ||
|
|
5c635f17a7 | ||
|
|
c27d604780 | ||
|
|
ddaf575719 | ||
|
|
0757f2ea80 | ||
|
|
3f8dfb6516 | ||
|
|
45ee25adc2 | ||
|
|
fcfb3d4083 | ||
|
|
357f8bd684 | ||
|
|
173cd8b4ad | ||
|
|
d2ca5994c8 | ||
|
|
fe04d177af | ||
|
|
2a6b18bc1b | ||
|
|
152cd272f6 | ||
|
|
abbdc139e8 | ||
|
|
7226019b6a | ||
|
|
78ea29bd48 | ||
|
|
5c90d1f769 | ||
|
|
2ecea40fdc | ||
|
|
8d9c11b6f2 | ||
|
|
afd23e3457 | ||
|
|
ddeaad74d0 | ||
|
|
a82472f509 | ||
|
|
4a43e376ed | ||
|
|
9ab82e1e5e | ||
|
|
4b28d0c38b | ||
|
|
1924cd1822 | ||
|
|
730b2dd874 | ||
|
|
1c2b383838 | ||
|
|
0b99f83d6f | ||
|
|
dc5b3f48e8 | ||
|
|
d17671c99e | ||
|
|
b69259e83b | ||
|
|
f2c80dc815 | ||
|
|
cbc281b173 | ||
|
|
472f859bd8 | ||
|
|
e71f650650 | ||
|
|
e64c9d9cd4 | ||
|
|
87a1c6621c | ||
|
|
09f49c4afa | ||
|
|
eba1b2f999 | ||
|
|
8e026279ac | ||
|
|
3605e99914 | ||
|
|
5e9d479cef | ||
|
|
fb20463449 | ||
|
|
f5db73342f | ||
|
|
9b2dc23197 | ||
|
|
067052e1a8 | ||
|
|
441e5fbc05 | ||
|
|
0a2aae3bc2 | ||
|
|
a76c798500 | ||
|
|
c238ebeef4 | ||
|
|
206eddbdbd | ||
|
|
45549cdbcf | ||
|
|
e043b6a8c5 | ||
|
|
677076b89d | ||
|
|
77c9b99f74 | ||
|
|
c8cdda0c18 | ||
|
|
41cd73703e | ||
|
|
607e84c052 | ||
|
|
8416390631 | ||
|
|
6094d25cfb | ||
|
|
c80504efae | ||
|
|
2589033523 | ||
|
|
f74fed5ee0 | ||
|
|
01e295084c | ||
|
|
9f923941a8 | ||
|
|
3aba03eb28 | ||
|
|
1107195ab6 | ||
|
|
b67384cdd1 | ||
|
|
59603b550e | ||
|
|
c52f714149 | ||
|
|
35420732d1 | ||
|
|
7f48b84292 | ||
|
|
5110c5706f | ||
|
|
633511acda | ||
|
|
75d0a69c02 | ||
|
|
e6fe9396f8 | ||
|
|
5c4107835b | ||
|
|
57cc90f89f | ||
|
|
7e08e48a8a | ||
|
|
87719f1161 | ||
|
|
99d7c8d824 | ||
|
|
1caf4f99cf | ||
|
|
78b3887e4a | ||
|
|
bb064aacd5 | ||
|
|
d9e8321d9e | ||
|
|
5f6df95e30 | ||
|
|
ef779c346e | ||
|
|
c1543d4a2f | ||
|
|
9145e27009 | ||
|
|
299f1a9102 | ||
|
|
6eab438778 | ||
|
|
2a623fbb8c | ||
|
|
7bde091f1b | ||
|
|
412baf7872 | ||
|
|
010057d9e1 | ||
|
|
6c7d8df565 | ||
|
|
92598da06f | ||
|
|
601d6f7c6c | ||
|
|
e13cebaa3a | ||
|
|
5724868c5a | ||
|
|
03972f0aec | ||
|
|
6143ec4180 | ||
|
|
c30464ee77 | ||
|
|
1e1e6456cd | ||
|
|
6c46bb4756 | ||
|
|
5ac6b1d1f1 | ||
|
|
9c95c9c6e9 | ||
|
|
4b052e74f1 | ||
|
|
bd6b8e4b7f | ||
|
|
26e8432bb0 | ||
|
|
05f9fd22fe | ||
|
|
8f5bde2797 | ||
|
|
77fe16faec | ||
|
|
38dd776049 | ||
|
|
7e3c4074e4 | ||
|
|
89c8f8e80f | ||
|
|
7397e681f4 | ||
|
|
e7d5afe90e | ||
|
|
2d9d354a05 | ||
|
|
60a686d75c | ||
|
|
89b70843c7 | ||
|
|
617987f4a5 | ||
|
|
5bb338d6ec | ||
|
|
8003d91c4a | ||
|
|
a4ef18ddab | ||
|
|
82e55b4761 | ||
|
|
3a51c386ca | ||
|
|
9868519cc0 | ||
|
|
038c007915 | ||
|
|
ba6350e24d | ||
|
|
1c866931f3 | ||
|
|
b7aff28b5e | ||
|
|
f0897ffdf8 | ||
|
|
f5937a6fad | ||
|
|
7dc77ed609 | ||
|
|
d1bfe62a65 | ||
|
|
f7c8f24bff | ||
|
|
055d63eeb5 | ||
|
|
ef7f92ed15 | ||
|
|
8daa53dd3e | ||
|
|
109003ae12 | ||
|
|
36f1abdbe6 | ||
|
|
50519776e0 | ||
|
|
9e289574ff | ||
|
|
2d96b06d4c | ||
|
|
0c706cfed2 | ||
|
|
3f9e1c6c47 | ||
|
|
58c08cebd7 | ||
|
|
fed0703ecf | ||
|
|
aaed1a934e | ||
|
|
11639af26e | ||
|
|
623ad2b1dc | ||
|
|
ac68fec6b1 | ||
|
|
dad49354aa | ||
|
|
ce4fa3c027 | ||
|
|
02d1a12bf5 | ||
|
|
78cbc3871e | ||
|
|
f9bb882ceb | ||
|
|
87fc6bde1d | ||
|
|
f666367b5d | ||
|
|
81c02f24c6 | ||
|
|
1d01ed92d8 | ||
|
|
1a7ced34a7 | ||
|
|
47ab94b5bc | ||
|
|
bea0778acc | ||
|
|
eeba5087b1 | ||
|
|
7bd8bf02f6 | ||
|
|
378f87a634 | ||
|
|
76dd5383ab | ||
|
|
129ca185e4 | ||
|
|
1f4ef42625 | ||
|
|
eb55e4def6 | ||
|
|
acda834ad9 | ||
|
|
aba3e30d9c | ||
|
|
c9e55ff1dc | ||
|
|
013c5e9e58 | ||
|
|
5e7f3dc24a | ||
|
|
2ffa174900 | ||
|
|
26cbe17218 | ||
|
|
fc8f1dca4e | ||
|
|
6b20a4f8f7 | ||
|
|
091025a304 | ||
|
|
1180789c0c | ||
|
|
ac056d4a06 | ||
|
|
7c196e098a | ||
|
|
c612b13529 | ||
|
|
5f4c79f6c4 | ||
|
|
302cbf641e | ||
|
|
4bc2e9ad44 | ||
|
|
6853c4c212 | ||
|
|
800e15cfd0 | ||
|
|
873f184117 | ||
|
|
3bae5cd5cb | ||
|
|
19300332e8 | ||
|
|
89d88c9644 | ||
|
|
472014b1b1 | ||
|
|
c094420428 | ||
|
|
e948064063 | ||
|
|
bdd3b7d2a5 | ||
|
|
404c37b54c | ||
|
|
ac686a17fa | ||
|
|
bfc5068b5e | ||
|
|
649bee2330 | ||
|
|
593587ea60 | ||
|
|
0ab81a7b32 | ||
|
|
8bf937357c | ||
|
|
1e8b1fda81 | ||
|
|
cba9212150 | ||
|
|
a0a6c42227 | ||
|
|
c245d9de80 | ||
|
|
ca5cd8e09b | ||
|
|
34ac4cd23b | ||
|
|
8598fe8ad9 | ||
|
|
3699a31f28 | ||
|
|
06498672ab | ||
|
|
cd3b92af7a | ||
|
|
e15cc29e2f | ||
|
|
dabeb066de | ||
|
|
c59e7cc456 | ||
|
|
3c16bfef58 | ||
|
|
ec24977d4e | ||
|
|
10b4f57a0e | ||
|
|
75cd5a75e1 | ||
|
|
fb2d0ed170 | ||
|
|
d4e139eaf5 | ||
|
|
49ef1276f6 | ||
|
|
828788b643 | ||
|
|
0ca8d7a258 | ||
|
|
75de49d151 | ||
|
|
d39e1c0c6e | ||
|
|
8e7f201ca8 | ||
|
|
85b2a9b0a0 | ||
|
|
d4e40faecc | ||
|
|
0be913ab18 | ||
|
|
bab30f3b61 | ||
|
|
abb599c556 | ||
|
|
2b6c6e9ab3 | ||
|
|
0db0d744da | ||
|
|
6b9f2d29fc | ||
|
|
2c141a5255 | ||
|
|
4f69a32700 | ||
|
|
3d4c08ad20 | ||
|
|
594d415976 | ||
|
|
ae50fde4e1 | ||
|
|
401461f1f0 | ||
|
|
676c6e106d | ||
|
|
d0a066ccd3 | ||
|
|
340e286277 | ||
|
|
9211d41984 | ||
|
|
686aa457d6 | ||
|
|
1b1431f0d0 | ||
|
|
cbcb46597d | ||
|
|
6644c85978 | ||
|
|
dd781fd86b | ||
|
|
97209846ad | ||
|
|
9744aa79b3 | ||
|
|
801648fcfe | ||
|
|
181fb9edb6 | ||
|
|
9ddbdfb80a | ||
|
|
1cab88181d | ||
|
|
803cc6e402 | ||
|
|
2e0b9f5ea5 | ||
|
|
fe60311072 | ||
|
|
de4a1d1e93 | ||
|
|
de36a2f4d1 | ||
|
|
57f6f3b318 | ||
|
|
8a3eb0d83b | ||
|
|
dd68b37a2a | ||
|
|
96793b39ba | ||
|
|
aa4f91b3b0 | ||
|
|
9ad44e6dc2 | ||
|
|
988b3d27b8 | ||
|
|
043cc13bb3 | ||
|
|
c0ee5f6563 | ||
|
|
f02d0ab1dd | ||
|
|
ff6d17990f | ||
|
|
9be52f639d | ||
|
|
586c419c46 | ||
|
|
73f02cc1bf | ||
|
|
eb27d24923 | ||
|
|
eeb056d509 | ||
|
|
3615912ce0 | ||
|
|
4518dfc0ca | ||
|
|
d5a7e8938d | ||
|
|
3d15844f7c | ||
|
|
62ed21b316 | ||
|
|
a178d43b01 | ||
|
|
ef2152beea | ||
|
|
e6475c1a9e | ||
|
|
091debd2a7 | ||
|
|
e7eff3e585 | ||
|
|
86a90e39d2 | ||
|
|
2887fbb251 | ||
|
|
fa6177b56d | ||
|
|
78524cdde7 | ||
|
|
6077d3f4c3 | ||
|
|
fd3acc456d | ||
|
|
257fe6dd3f | ||
|
|
cbb7cc93e9 | ||
|
|
8b608c5cce | ||
|
|
163fb14d0e | ||
|
|
e4f87bd805 | ||
|
|
864c7d96f1 | ||
|
|
e35e87fb05 | ||
|
|
3246bdbc8b | ||
|
|
710bb5fe1c | ||
|
|
53a52da709 | ||
|
|
4afbfc6e57 | ||
|
|
f7aa9efe13 | ||
|
|
1fce0ee551 | ||
|
|
ee695f9000 | ||
|
|
8f99ebb577 | ||
|
|
5bfafaf241 | ||
|
|
64e1a2ce4b | ||
|
|
27883c5beb | ||
|
|
2772725fe3 | ||
|
|
3f58a64827 | ||
|
|
cb37c7d218 | ||
|
|
45d951071f | ||
|
|
2ac60684fa | ||
|
|
af71742110 | ||
|
|
1adf27427a | ||
|
|
082f78eefa | ||
|
|
a8a9ec0023 | ||
|
|
df4f945bc6 | ||
|
|
ad756a5b86 | ||
|
|
2ca99ba669 | ||
|
|
6443575114 | ||
|
|
52a51d81f1 | ||
|
|
7d20b06e52 | ||
|
|
ad9d8a7200 | ||
|
|
44da699343 | ||
|
|
0a542f9be9 | ||
|
|
8f1601ca55 | ||
|
|
ff0ce164da | ||
|
|
3650c38aa9 | ||
|
|
61d97338e2 | ||
|
|
e27bb4de50 | ||
|
|
4462d8bf22 | ||
|
|
f23a8ac928 | ||
|
|
9e1750ee13 | ||
|
|
5cd284cb36 | ||
|
|
5ea58430fe | ||
|
|
acfbda0d91 | ||
|
|
437e5a62f1 | ||
|
|
bbb5aea77d | ||
|
|
b5d04f84e7 | ||
|
|
93b12eb14f | ||
|
|
0aa67c4cf2 | ||
|
|
1c1affac5c | ||
|
|
0919797edc | ||
|
|
94ac8fd837 | ||
|
|
ecc82b5b51 | ||
|
|
add021fd67 | ||
|
|
2daf309d7d | ||
|
|
20be4a40ea | ||
|
|
7de91da87a | ||
|
|
b8b48b533b | ||
|
|
535f981739 | ||
|
|
0404ac0395 | ||
|
|
f961625f03 | ||
|
|
7909bae065 | ||
|
|
7e6e117b17 | ||
|
|
314e64d7b1 | ||
|
|
8c72e77e0d | ||
|
|
77eb6b358f | ||
|
|
25f86e2542 | ||
|
|
8bc0461463 | ||
|
|
c9dd1ea71d | ||
|
|
85d3529bc3 | ||
|
|
3aac8bb072 | ||
|
|
e9a7e21aca | ||
|
|
fc0e4da33c | ||
|
|
ef3c1315dd | ||
|
|
5c12890faf | ||
|
|
c4fd5e0999 | ||
|
|
d3c56d635d | ||
|
|
c53cb49034 | ||
|
|
d38463c5d9 | ||
|
|
bc4b424f0e | ||
|
|
6bd4f3b7b4 | ||
|
|
d91d83f115 | ||
|
|
584b6ab219 | ||
|
|
04b155dbfb | ||
|
|
ddc5488db7 | ||
|
|
5c6906d5fd | ||
|
|
56fee5d286 | ||
|
|
c4dcab04b6 | ||
|
|
8dd9a96769 | ||
|
|
6864015af6 | ||
|
|
ebaa60e929 | ||
|
|
ac9962c624 | ||
|
|
ccd1773643 | ||
|
|
ac970ff768 | ||
|
|
53ff45425d | ||
|
|
de435c741f | ||
|
|
1dc9a44aec | ||
|
|
0206379315 | ||
|
|
c39adbbbc5 | ||
|
|
061a15d4f7 | ||
|
|
7c2518f027 | ||
|
|
a07459454b | ||
|
|
752f9d2694 | ||
|
|
0a180f9e20 | ||
|
|
6fff2e1c2f | ||
|
|
4266900d2e | ||
|
|
a2a1d84bc0 | ||
|
|
7648c562f1 | ||
|
|
403fd30926 | ||
|
|
17ccddf541 | ||
|
|
c24366fcec | ||
|
|
9a258a6265 | ||
|
|
4542ccbdc2 | ||
|
|
41d8ad2b86 | ||
|
|
a3a2ee118e | ||
|
|
f285efc277 | ||
|
|
fbaf87ec6c | ||
|
|
a005a468a7 | ||
|
|
c8d01549ba | ||
|
|
23af4505dd | ||
|
|
53cb6a5b3e | ||
|
|
765384ace1 | ||
|
|
d39e3e2f69 | ||
|
|
d75124066e | ||
|
|
f4400876da | ||
|
|
c222c5127f | ||
|
|
90d04956e6 | ||
|
|
5d3b918339 | ||
|
|
eba0091764 | ||
|
|
7999cbbea3 | ||
|
|
cee7987b35 | ||
|
|
d475c08024 | ||
|
|
d78f83ae49 | ||
|
|
df694f01e7 | ||
|
|
f5d9cbb11b | ||
|
|
d802588abe | ||
|
|
927d6d2fc6 | ||
|
|
bab7595078 | ||
|
|
1ea9a6c57f | ||
|
|
f479d415d8 | ||
|
|
f41e7be726 | ||
|
|
c1fe28124f | ||
|
|
01a1e506e4 | ||
|
|
e61aebb55d | ||
|
|
bae5bafc62 | ||
|
|
be6cb266de | ||
|
|
6f72edc2d9 | ||
|
|
29f66bdfed | ||
|
|
c1c6f5682c | ||
|
|
a8a74a0246 | ||
|
|
f9b867c3eb | ||
|
|
6c07f9a048 | ||
|
|
573e0acafa | ||
|
|
530c664967 | ||
|
|
12c29c8387 | ||
|
|
6c7f61d90f | ||
|
|
eb6d8d0bb3 | ||
|
|
ef462973c7 | ||
|
|
aa7e49cfde | ||
|
|
186048676a | ||
|
|
0f40721cff | ||
|
|
3799dd92fd | ||
|
|
9ea1a2b1de | ||
|
|
d8b27d122e | ||
|
|
15c3a6eae4 | ||
|
|
4fcc90d53e | ||
|
|
2619a50976 | ||
|
|
de116c95d4 | ||
|
|
47594c9b07 | ||
|
|
1b9098b564 | ||
|
|
b0306eac87 | ||
|
|
125e813d25 | ||
|
|
8e0d4a70f2 | ||
|
|
0556fc68b0 | ||
|
|
e9e2313159 | ||
|
|
dbb2f03a43 | ||
|
|
ed0dc2d887 | ||
|
|
0a93f2d0b0 | ||
|
|
fd581cd65c | ||
|
|
36702bc971 | ||
|
|
0fc3eb38f1 | ||
|
|
73c8e945e9 | ||
|
|
b8420303ba | ||
|
|
532bfcf937 | ||
|
|
f6d892023e | ||
|
|
7f2977f4af | ||
|
|
e246a2753b | ||
|
|
6d06a26c31 | ||
|
|
7cda21b60f | ||
|
|
1db740039b | ||
|
|
df462774aa | ||
|
|
f55fa789c4 | ||
|
|
488541f26e | ||
|
|
b404b1cf50 | ||
|
|
69370aa806 | ||
|
|
b3ddd85b09 | ||
|
|
033221fd01 | ||
|
|
1201fa38a8 | ||
|
|
32059c376e | ||
|
|
511d393c78 | ||
|
|
237601cf29 | ||
|
|
928f14872e | ||
|
|
c6a9adf96c | ||
|
|
cdd00e0bd4 | ||
|
|
2503d13402 | ||
|
|
2d97970d24 | ||
|
|
d8945e53ba | ||
|
|
f18012d07b | ||
|
|
104f8603b9 | ||
|
|
19bb083030 | ||
|
|
ae575a19a0 | ||
|
|
5f94d0a5c2 | ||
|
|
f2fe1a7c17 | ||
|
|
618e6a1604 | ||
|
|
cef294513a | ||
|
|
8550abadef | ||
|
|
25c8b2d631 | ||
|
|
e7b690205e | ||
|
|
2403f8833a | ||
|
|
9f25743b4d | ||
|
|
f1c76f65cf | ||
|
|
7b39533410 | ||
|
|
1fe9c39dd5 | ||
|
|
2b7be2e3f1 | ||
|
|
1ee9a3f279 | ||
|
|
679acfb239 | ||
|
|
3c7ab4b49b | ||
|
|
3f062e1c0a | ||
|
|
0a497d8c3f | ||
|
|
462351f485 | ||
|
|
c4cbb631a9 | ||
|
|
76f2d15b1b | ||
|
|
5d63865ae5 | ||
|
|
ad7c88fb45 | ||
|
|
1af3877758 | ||
|
|
146ce3b12b | ||
|
|
bf67721ac4 | ||
|
|
436f736a8e | ||
|
|
1446e90dd9 | ||
|
|
54716c03d1 | ||
|
|
fad5e97e0a | ||
|
|
8cf79590c5 | ||
|
|
c6aafac6c8 | ||
|
|
94a5995197 | ||
|
|
74081dc981 | ||
|
|
7a593b13a7 | ||
|
|
25dc38b2b1 | ||
|
|
403b12f7b7 | ||
|
|
cd223be88a | ||
|
|
a6a3fca1e7 | ||
|
|
63815eb5c2 | ||
|
|
4444264830 | ||
|
|
255f91f7ca | ||
|
|
700207d29b | ||
|
|
72d96e9a3b | ||
|
|
a55b6de1b4 | ||
|
|
9faac05666 | ||
|
|
7bf408aba0 | ||
|
|
70ee391b33 | ||
|
|
f5eb1158db | ||
|
|
81916d0f45 | ||
|
|
76d77cc244 | ||
|
|
03dccbe613 | ||
|
|
1ccec65619 | ||
|
|
b78e9062a1 | ||
|
|
dc87354702 | ||
|
|
033e8583db | ||
|
|
d46b51fa9a | ||
|
|
dd1dfdcfeb | ||
|
|
c0233c803d | ||
|
|
1fa8c88f6b | ||
|
|
195b109a36 | ||
|
|
994ac3fc8e | ||
|
|
a8ec5cf5e9 | ||
|
|
b4263aa3ec | ||
|
|
6456809a72 | ||
|
|
024c0a3186 | ||
|
|
560b1d48bc | ||
|
|
bb2d860807 | ||
|
|
6f1c0fa4a8 | ||
|
|
35808e7bde | ||
|
|
1b4eeb35b3 | ||
|
|
cee032bbc9 | ||
|
|
9547b70743 | ||
|
|
5df8939d84 | ||
|
|
0b9c689273 | ||
|
|
ac193cd97a | ||
|
|
7355a60f16 | ||
|
|
fb6f6325b5 | ||
|
|
0f767d9dfc | ||
|
|
d1addcab91 | ||
|
|
7a02cd794b | ||
|
|
258cd9209f | ||
|
|
700f6022d9 | ||
|
|
a0f6b815e8 | ||
|
|
54477b8b8f | ||
|
|
6183f8d4e9 | ||
|
|
d98e247665 | ||
|
|
d0cd976246 | ||
|
|
15f6f6a90a | ||
|
|
f18074bf49 | ||
|
|
3f64b74453 | ||
|
|
1c86256f83 | ||
|
|
98105ad4dc | ||
|
|
6bf2d6c1ab | ||
|
|
cebd51aac1 | ||
|
|
42b8b3aa75 | ||
|
|
c0eaf79d25 | ||
|
|
fb2fe3eb63 | ||
|
|
184d63f7eb | ||
|
|
14ebd58c96 | ||
|
|
3edddb8e91 | ||
|
|
d2340dff1a | ||
|
|
d30c6aec7d | ||
|
|
4854b3e08f | ||
|
|
facc748090 | ||
|
|
5c71612e34 | ||
|
|
f00a5fbaf4 | ||
|
|
34769da9cc | ||
|
|
458e6ed531 | ||
|
|
05814e9351 | ||
|
|
b6c0b1b396 | ||
|
|
1a299d85b6 | ||
|
|
1816274ebe | ||
|
|
b02e4c5308 | ||
|
|
12ed42e163 | ||
|
|
6c9c135062 | ||
|
|
6aea0160df | ||
|
|
30edd9e8dd | ||
|
|
7734402e8e | ||
|
|
ace1332ca5 | ||
|
|
baa313e8f8 | ||
|
|
3d9aa24f3c | ||
|
|
c287d457d6 | ||
|
|
7537b27a58 | ||
|
|
1f560c92fc | ||
|
|
74cd11c763 | ||
|
|
ee7d89331b | ||
|
|
5b51077823 | ||
|
|
d2f94d1945 | ||
|
|
0170a099bb | ||
|
|
2b893d0ea1 | ||
|
|
a08da57cbb | ||
|
|
a313fe5a86 | ||
|
|
544de699f6 | ||
|
|
d9d35b7f50 | ||
|
|
7dbec00f0e | ||
|
|
51b9b40c7e | ||
|
|
b2383a084a | ||
|
|
61848c5e24 | ||
|
|
60b2f7d243 | ||
|
|
d2a5e126ae | ||
|
|
9d7d8ddde9 | ||
|
|
249ebeb4aa | ||
|
|
378c14d790 | ||
|
|
30c1093c2f | ||
|
|
b966c8cb0b | ||
|
|
38cddf029b | ||
|
|
6652a27781 | ||
|
|
08b067cfc5 | ||
|
|
4959b84b4c | ||
|
|
d42562fdcb | ||
|
|
1e0aacd811 | ||
|
|
329469fe84 | ||
|
|
93b0c5a7e5 | ||
|
|
9e456fd315 | ||
|
|
8203c71b03 | ||
|
|
80d1e9a9a7 | ||
|
|
3c27e57b19 | ||
|
|
b46b2f048d | ||
|
|
8d403f579c | ||
|
|
12b274ab43 | ||
|
|
986cccb800 | ||
|
|
5da8728d6c | ||
|
|
66c9a1e90a | ||
|
|
a1925e1d0b | ||
|
|
da0910b1e5 | ||
|
|
8dbbe34dfc | ||
|
|
947d552509 | ||
|
|
037bc4ea2b | ||
|
|
cfbef141d8 | ||
|
|
410ad0dc09 | ||
|
|
3d9b42f0c2 | ||
|
|
eeea21c882 | ||
|
|
d94e5480c9 | ||
|
|
a5c8e87e6d | ||
|
|
08f74201b6 | ||
|
|
3a25c683b6 | ||
|
|
fb43f8bf1a | ||
|
|
bf082600c3 | ||
|
|
7596c66703 | ||
|
|
e1bcf6c47e | ||
|
|
ef1d6c78ac | ||
|
|
f36406af08 | ||
|
|
4b7d215d9d | ||
|
|
e09f12dc0b | ||
|
|
efa7406741 | ||
|
|
5a2181c064 | ||
|
|
dda81b12da | ||
|
|
c4bf67d133 | ||
|
|
f355c8679d | ||
|
|
095864de39 | ||
|
|
0eb196e852 | ||
|
|
62e5d8d841 | ||
|
|
2f82fb842d | ||
|
|
f9fa6abbad | ||
|
|
4b55a69150 | ||
|
|
42e12fae24 | ||
|
|
2f55d5130a | ||
|
|
cd4b47b397 | ||
|
|
126cba1e94 | ||
|
|
e4e894fa54 | ||
|
|
65f701d459 | ||
|
|
c2b7cc7964 | ||
|
|
969d1dff2a | ||
|
|
e0fab808b9 | ||
|
|
77031d7082 | ||
|
|
5a0f7ea706 | ||
|
|
9d041c4b62 | ||
|
|
ec4d29c327 | ||
|
|
5f8d8988e3 | ||
|
|
a31912f710 | ||
|
|
e0d42962e7 | ||
|
|
47688bf016 | ||
|
|
39a695959d | ||
|
|
71d45a67b7 | ||
|
|
8dc1369ba3 | ||
|
|
1206654d98 | ||
|
|
1492763d0c | ||
|
|
285fafc464 | ||
|
|
b15cc59b95 | ||
|
|
55cfe31dfe | ||
|
|
dafa6e0310 | ||
|
|
4befe17cab | ||
|
|
2217b560cb | ||
|
|
39d01a1da8 | ||
|
|
4e14ad7821 | ||
|
|
aee5a2cbe4 | ||
|
|
de2d5aec1a | ||
|
|
bea03de562 | ||
|
|
0aeccbb02c | ||
|
|
4e11152e76 | ||
|
|
5059028e8a | ||
|
|
070e6cf848 | ||
|
|
4187fe7261 | ||
|
|
bdec147874 | ||
|
|
189fc76dad | ||
|
|
c0a68005d4 | ||
|
|
8187e4cdc2 | ||
|
|
cc98fd4167 | ||
|
|
422e014eff | ||
|
|
dd4e044608 | ||
|
|
13fad1ca47 | ||
|
|
70aaa29016 | ||
|
|
7dcec9afcf | ||
|
|
608642fc25 | ||
|
|
dd62c08b09 | ||
|
|
315fc21eca | ||
|
|
8cfd3aeda9 | ||
|
|
6061323c36 | ||
|
|
17da8b04bf | ||
|
|
3a948406d5 | ||
|
|
91c52f8ec8 | ||
|
|
6076bbb554 | ||
|
|
f70d3359c4 | ||
|
|
2dbfbe36c3 | ||
|
|
a25bd03566 | ||
|
|
109da1c2cc | ||
|
|
9a48e25bb1 | ||
|
|
a3ade200a9 | ||
|
|
eb1901f03d | ||
|
|
f8c7cdfc28 | ||
|
|
ea58d84b55 | ||
|
|
1360caaa81 | ||
|
|
315a7b9e6a | ||
|
|
79311d107b | ||
|
|
77435fcbd7 | ||
|
|
4395c07648 | ||
|
|
f9cc77382a | ||
|
|
e4fce08eff | ||
|
|
47196cbc55 | ||
|
|
c0bf301434 | ||
|
|
f6671f8561 | ||
|
|
26aab2961b | ||
|
|
c233abe3a4 | ||
|
|
beb3646bef | ||
|
|
eb8330a420 | ||
|
|
7f3c2176f8 | ||
|
|
1d0fdd68f4 | ||
|
|
b26c78828e | ||
|
|
85648c7f11 | ||
|
|
8588f446aa | ||
|
|
d723c5c1c7 | ||
|
|
7897f2489c | ||
|
|
86e2e0a2c3 | ||
|
|
e2349019a3 | ||
|
|
e485044cfb | ||
|
|
72d80e2857 | ||
|
|
79a4733698 | ||
|
|
c64e1daa99 | ||
|
|
d6b4933b05 | ||
|
|
bcf9cee0f8 | ||
|
|
a2960a7997 | ||
|
|
dac9874895 | ||
|
|
a4d374ecb6 | ||
|
|
3e56c8ed8b | ||
|
|
f4c11d14e7 | ||
|
|
dde805931d | ||
|
|
fedc721432 | ||
|
|
b09cb24804 | ||
|
|
80bfeb1a29 | ||
|
|
63ad51a637 | ||
|
|
5ee6232bfc | ||
|
|
b7d399faf7 | ||
|
|
28691bb8a0 | ||
|
|
fd61f35f78 | ||
|
|
0505b59781 | ||
|
|
a3601e5167 | ||
|
|
99f63ab64d | ||
|
|
bb2a9fe180 | ||
|
|
e9c7c7ee4c | ||
|
|
ff7d4f237a | ||
|
|
8bf9f8057e | ||
|
|
27b0f531d7 | ||
|
|
01e96d3086 | ||
|
|
95e7a7c8eb | ||
|
|
bbc2f4766e | ||
|
|
12dde5154f | ||
|
|
5bc3609ad7 | ||
|
|
5c1cdea3c6 | ||
|
|
3da5b3a278 | ||
|
|
9f6460ddae | ||
|
|
f1dcf85d7b | ||
|
|
7b78edf38f | ||
|
|
4a726a0c75 | ||
|
|
5afa7e9fc3 | ||
|
|
75c102f304 | ||
|
|
68f3c10055 | ||
|
|
e9aeda2a34 | ||
|
|
45c7a110b6 | ||
|
|
e438e6aedb | ||
|
|
8dd888bcbd | ||
|
|
5f12291ac4 | ||
|
|
e206d78a52 | ||
|
|
d94a82b0a7 | ||
|
|
3bee3d1701 | ||
|
|
631352e1ef | ||
|
|
f2d5dee49e | ||
|
|
2d578c7cd9 | ||
|
|
b07ff100d1 | ||
|
|
b0c10b2248 | ||
|
|
7b8249442d | ||
|
|
8221339318 | ||
|
|
b773af96b8 | ||
|
|
dbc7808c70 | ||
|
|
77c5000f16 | ||
|
|
56928b6ca0 | ||
|
|
8178dfe7bd | ||
|
|
0db6c667c6 | ||
|
|
2b0a642827 | ||
|
|
f71c0f8cb8 | ||
|
|
3782372f65 | ||
|
|
9e35e42030 | ||
|
|
7a8195e54c | ||
|
|
b50ee28693 | ||
|
|
e134f910aa | ||
|
|
17eb6e47eb | ||
|
|
82ce626d8a | ||
|
|
e063ff5e54 | ||
|
|
5aa98966bc | ||
|
|
fca2746a82 | ||
|
|
7faffc2de7 | ||
|
|
86a8a9dc85 | ||
|
|
c13a0c1c26 | ||
|
|
6ce3c070b8 | ||
|
|
e1cb11da22 | ||
|
|
c628ea34b7 | ||
|
|
33b7c726f6 | ||
|
|
6f3fd424e1 | ||
|
|
b048ce9c8e | ||
|
|
6a1040d659 | ||
|
|
fc05c28231 | ||
|
|
5d165bc6df | ||
|
|
c139b5eabb | ||
|
|
d6dd0e551d | ||
|
|
a84a7bb7c3 | ||
|
|
7195ae6e46 | ||
|
|
3c70b2f033 | ||
|
|
c46340a830 | ||
|
|
8d703b1459 | ||
|
|
f1145ef169 | ||
|
|
a28f9ceeba | ||
|
|
1927bb4798 | ||
|
|
d4c77f9786 | ||
|
|
021f8d0331 | ||
|
|
e7d25550ee | ||
|
|
8227276437 | ||
|
|
38ef76bcdc | ||
|
|
7faa0f60b4 | ||
|
|
faebbb2b8a | ||
|
|
bc9beb1e87 | ||
|
|
f161f723d7 | ||
|
|
6c94550de3 | ||
|
|
b39a45781a | ||
|
|
9273c9ee10 | ||
|
|
e6285914b0 | ||
|
|
7d2eb83a1b | ||
|
|
7e622f1153 | ||
|
|
9b30a634b1 | ||
|
|
e897d2ef81 | ||
|
|
f5b177dda8 | ||
|
|
5f51b5d565 | ||
|
|
8d7753fb40 | ||
|
|
4c6f5744d8 | ||
|
|
43eb2932ba | ||
|
|
59de0e7a4a | ||
|
|
7b204cf910 | ||
|
|
b587a19a0f | ||
|
|
e58420b031 | ||
|
|
24313d6709 | ||
|
|
7acb30b981 | ||
|
|
7454620aa2 | ||
|
|
abb0799817 | ||
|
|
aebd28dbef | ||
|
|
152db9fb8e | ||
|
|
980cd520cd | ||
|
|
373ec8b5a1 | ||
|
|
bacb78e9e8 | ||
|
|
333e112e37 | ||
|
|
818d5d782f | ||
|
|
8774090086 | ||
|
|
0ca65c6d61 | ||
|
|
c38daac646 | ||
|
|
c31f5b1d59 | ||
|
|
2320a56a63 | ||
|
|
07bd91b456 | ||
|
|
803f7c3a4b | ||
|
|
a1926982c6 | ||
|
|
b3306d50eb | ||
|
|
3e6e5f95ec | ||
|
|
df71fb42ac | ||
|
|
fe81a354a3 | ||
|
|
67ba2e49c3 | ||
|
|
8dec42f295 | ||
|
|
5942553370 | ||
|
|
ae5d31746e | ||
|
|
1c1976abbf | ||
|
|
b3465ddf89 | ||
|
|
f67fafdb8e | ||
|
|
ffcac677e0 | ||
|
|
a9d643aa55 | ||
|
|
fa3647e8e5 | ||
|
|
db839f6d6d | ||
|
|
3a85d5b89f | ||
|
|
1575c2bc10 | ||
|
|
57189183d6 | ||
|
|
01c5cf98df | ||
|
|
9ad2995168 | ||
|
|
51fc6518e1 | ||
|
|
530db4e904 | ||
|
|
cd92ebd69c | ||
|
|
5c58552d91 | ||
|
|
584aebf76b | ||
|
|
43a2480699 | ||
|
|
699503042f | ||
|
|
02e71c23de | ||
|
|
c24c3868bb | ||
|
|
5c60819668 | ||
|
|
480a125a20 | ||
|
|
598a6ebaed | ||
|
|
185ad5e747 | ||
|
|
42d7e4312b | ||
|
|
41a741724f | ||
|
|
500fa7f314 | ||
|
|
d23d18933c | ||
|
|
f0e736962d | ||
|
|
88d14792a4 | ||
|
|
6fd2d12d3f | ||
|
|
a32f55d64e | ||
|
|
be129d243b | ||
|
|
c85355986b | ||
|
|
ec5454de44 | ||
|
|
0b7bb29557 | ||
|
|
8f1a498953 | ||
|
|
293ed9065d | ||
|
|
0e17658269 | ||
|
|
87edda5972 | ||
|
|
ab8c3c2b75 | ||
|
|
44cef002ed | ||
|
|
bc80fe693a | ||
|
|
1898b51dc2 | ||
|
|
a5dcf5958e | ||
|
|
a0cbc5822e | ||
|
|
65ffce8db2 | ||
|
|
f241f12ddb | ||
|
|
9545e0145f | ||
|
|
15d3e4023b | ||
|
|
205e4dedd1 | ||
|
|
324d796aca | ||
|
|
5b31e8e9d8 | ||
|
|
0afb62ee88 | ||
|
|
1d3a8bcacd | ||
|
|
dd187aef08 | ||
|
|
78b2c4d094 | ||
|
|
83876532d0 | ||
|
|
edc824d05e | ||
|
|
9a7869dd40 | ||
|
|
1aa247d4da | ||
|
|
93e8ec7f06 | ||
|
|
ba1d1c3bac | ||
|
|
fbaff2b75b | ||
|
|
9774e8807b | ||
|
|
b4dd3a9889 | ||
|
|
4510b69798 | ||
|
|
84a3ee209a | ||
|
|
4b0fe879cb | ||
|
|
b87aeffcd8 | ||
|
|
88aafec6e3 | ||
|
|
2cb6ddf3ca | ||
|
|
799622a932 | ||
|
|
2283652704 | ||
|
|
33fb2daabf | ||
|
|
c79f59ec05 | ||
|
|
845aaad891 | ||
|
|
3c0b061220 | ||
|
|
af7de73f5c | ||
|
|
1a8a9f0047 | ||
|
|
a3b4e8db44 | ||
|
|
f9e8875803 | ||
|
|
e4c11d9a88 | ||
|
|
6d1d8da101 | ||
|
|
77303e668f | ||
|
|
72725b91c7 | ||
|
|
46f50f55c2 | ||
|
|
5e880f6bfa | ||
|
|
80cf88dcc1 | ||
|
|
dddc7f348f | ||
|
|
ce45f2e114 | ||
|
|
d22a7c0a63 | ||
|
|
59162155de | ||
|
|
0f347cd0b4 | ||
|
|
dc50793733 | ||
|
|
b98329f91c | ||
|
|
587d8fbfa9 | ||
|
|
26a2e9ef53 | ||
|
|
e5ca98a1d3 | ||
|
|
5bd5e9000e | ||
|
|
d566642c98 | ||
|
|
f81ddc60ec | ||
|
|
6e0b20c921 | ||
|
|
0c308b38c5 | ||
|
|
8e0fdf68e4 | ||
|
|
9c7a9b5674 | ||
|
|
0a49e31b30 | ||
|
|
9478206202 | ||
|
|
c86e6f5b88 | ||
|
|
87868acecd | ||
|
|
6d877754da | ||
|
|
dd5521a07c | ||
|
|
cca142ce86 | ||
|
|
fa3567cb9f | ||
|
|
7d22230b13 | ||
|
|
1b7f684ab2 | ||
|
|
fef8f4d29c | ||
|
|
0d7e945623 | ||
|
|
13ea8eae77 | ||
|
|
cdb2ef3ac7 | ||
|
|
f216e0198f | ||
|
|
c5c3698f43 | ||
|
|
299d3b9f60 | ||
|
|
eaaebeaebb | ||
|
|
2ef2d2b9c5 | ||
|
|
a856b4027b | ||
|
|
e2f72ecffc | ||
|
|
d888bcd473 | ||
|
|
0bfd5fa9a3 | ||
|
|
9c05ccef69 | ||
|
|
5281b6df77 | ||
|
|
cd5bb6a309 | ||
|
|
94a44d7af4 | ||
|
|
662050b89f | ||
|
|
3f7f3140eb | ||
|
|
dc11d18161 | ||
|
|
cde83e9d7d | ||
|
|
1476fda014 | ||
|
|
7a875f2db4 | ||
|
|
b868f37da5 | ||
|
|
9bef5a62dd | ||
|
|
d1aff30c9e | ||
|
|
a356dafde0 | ||
|
|
09477384fe | ||
|
|
6b923542e8 | ||
|
|
9722cf718c | ||
|
|
702b973c87 | ||
|
|
d66bbd95f0 | ||
|
|
aae498509b | ||
|
|
4987fb5f15 | ||
|
|
b1ed299286 | ||
|
|
679ac3e5b1 | ||
|
|
181ef788a3 | ||
|
|
d2caa0953f | ||
|
|
bd6d5ffbb9 | ||
|
|
a4e41a57d7 | ||
|
|
e8bb3864e3 | ||
|
|
3cd1066a8d | ||
|
|
6494b7ea89 | ||
|
|
2f5d13ac10 | ||
|
|
c8e6dca263 | ||
|
|
9dec40eca7 | ||
|
|
4896043599 | ||
|
|
5f0ddeedd0 | ||
|
|
e679e760ee | ||
|
|
1b0aa2a9b1 | ||
|
|
a49c230bb2 | ||
|
|
b65749cf70 | ||
|
|
0cf54a6a03 | ||
|
|
4d77277f2d | ||
|
|
8b81506ed3 | ||
|
|
24c6509a61 | ||
|
|
3c32c4160d | ||
|
|
1f912d86e1 | ||
|
|
9c93ab0b6d | ||
|
|
6646a20a5d | ||
|
|
92f471c476 | ||
|
|
4c7ce97650 | ||
|
|
4d1fc994c0 | ||
|
|
c4310c554e | ||
|
|
4ebcf40619 | ||
|
|
a884b94ac0 | ||
|
|
194e705982 | ||
|
|
c77ec3bf20 | ||
|
|
0a1857249b | ||
|
|
03bd5ad426 | ||
|
|
82d0f62d73 | ||
|
|
041fce743e | ||
|
|
ad4904a0ec | ||
|
|
42a1bca64d | ||
|
|
492a5bbb38 | ||
|
|
c40ef22c20 | ||
|
|
783aac561a | ||
|
|
02245c150c | ||
|
|
54a93775e4 | ||
|
|
ca778aac70 | ||
|
|
c75f1685c5 | ||
|
|
db5b103ab3 | ||
|
|
b2f1c9e832 | ||
|
|
cff4eebb74 | ||
|
|
a3aab5aeca | ||
|
|
63e5d8f7ea | ||
|
|
e4df7b10f6 | ||
|
|
f39a1e7d03 | ||
|
|
a473f6aa16 | ||
|
|
6907c24690 | ||
|
|
ef3d8a9277 | ||
|
|
1a183b7f17 | ||
|
|
ae6bcc2987 | ||
|
|
ed61e5c7f7 | ||
|
|
3581ac5ebd | ||
|
|
1f369b47d7 | ||
|
|
51ec699649 | ||
|
|
ed6be510fb | ||
|
|
55f05ba839 | ||
|
|
17c9382a21 | ||
|
|
50a53181dc | ||
|
|
36f6fec99f | ||
|
|
e9ae8063db | ||
|
|
9844550d6c | ||
|
|
82fcf08d84 | ||
|
|
b951624ed3 | ||
|
|
1b21f2d1c7 | ||
|
|
43ea0effa2 | ||
|
|
6df9b95dea | ||
|
|
cd7b9d31c9 | ||
|
|
cbb4565a48 | ||
|
|
fc2f5c3615 | ||
|
|
3a5ac9df10 | ||
|
|
e8497bb0cc | ||
|
|
fa6a5f75af | ||
|
|
7b9eb4c87c | ||
|
|
c3ab510b57 | ||
|
|
e9efe5bbb7 | ||
|
|
a7c234b272 | ||
|
|
80c8dcacb3 | ||
|
|
0ad2dea722 | ||
|
|
dec3c45f6c | ||
|
|
5736d2c9cc | ||
|
|
dc622a9bd5 | ||
|
|
3ddd1d5f21 | ||
|
|
ea15f0690c | ||
|
|
8751ef7439 | ||
|
|
8c393e3144 | ||
|
|
a3ef2a43ac | ||
|
|
d2b3029f67 | ||
|
|
3e800ec7c8 | ||
|
|
47d963b585 | ||
|
|
397ae3407e | ||
|
|
3f9e2d5d39 | ||
|
|
31449552d1 | ||
|
|
5f2fee5a99 | ||
|
|
bd71e779c3 | ||
|
|
18d83b8c50 | ||
|
|
49c7e7ce2d | ||
|
|
1af7d01d96 | ||
|
|
66c6938df0 | ||
|
|
bb9fb04788 | ||
|
|
f5cf2b2b55 | ||
|
|
43169ced69 | ||
|
|
708844857d | ||
|
|
ce777348b3 | ||
|
|
c052325487 | ||
|
|
ce69410f49 | ||
|
|
5f0be0cc7f | ||
|
|
dca6347ea9 | ||
|
|
94c4db9072 | ||
|
|
7f63be2cd2 | ||
|
|
1236dea4c5 | ||
|
|
c5c46df613 | ||
|
|
a96269150a | ||
|
|
75e347e8e3 | ||
|
|
d641cc5057 | ||
|
|
cf64145ff3 | ||
|
|
195eca6b09 | ||
|
|
b0ef077e48 | ||
|
|
e40a698d1a | ||
|
|
69c34b1b27 | ||
|
|
4700c2583d | ||
|
|
6cf79a3c73 | ||
|
|
2c110f7796 | ||
|
|
aebea0b57f | ||
|
|
8684455ea5 | ||
|
|
7cbb103376 | ||
|
|
9cf8ec5dd7 | ||
|
|
f5187b1533 | ||
|
|
6230a65a79 | ||
|
|
f13c96839e | ||
|
|
776f68c9dd | ||
|
|
714ed89a4c | ||
|
|
0b2b29b7cf | ||
|
|
a41c0a0519 | ||
|
|
026e28114e | ||
|
|
0840719289 | ||
|
|
aad875e113 | ||
|
|
6b059d94ba | ||
|
|
39eeaea7e7 | ||
|
|
81a8e91180 | ||
|
|
4fc6f6f59b | ||
|
|
dd2f573f27 | ||
|
|
ff8ddc431b | ||
|
|
d703945aa3 | ||
|
|
f4aba5f9ea | ||
|
|
393113c3e3 | ||
|
|
f2db18820a | ||
|
|
e04d300e18 | ||
|
|
685f836f41 | ||
|
|
d4f5ad641a | ||
|
|
007d437ee8 | ||
|
|
00e380e8d8 | ||
|
|
56749d9494 | ||
|
|
cf10d37c90 | ||
|
|
f9de34e36d | ||
|
|
aa889be140 | ||
|
|
6da6838bde | ||
|
|
07a0be303f | ||
|
|
ddbe0af290 | ||
|
|
e71f0f44a4 | ||
|
|
39f3bc7100 | ||
|
|
211a145e58 | ||
|
|
d2339b915f | ||
|
|
24c2651745 | ||
|
|
36e95b93b4 | ||
|
|
e7a44e0b15 | ||
|
|
fbdb21ac72 | ||
|
|
c9522cf6ae | ||
|
|
7bf9d334ad | ||
|
|
422ff93dcd | ||
|
|
eee1ea7a09 | ||
|
|
b90e482add | ||
|
|
875b51720b | ||
|
|
5272d4f9e3 | ||
|
|
c980f4f6e3 | ||
|
|
89525330c4 | ||
|
|
806db24c0e | ||
|
|
ba7d6df5a4 | ||
|
|
112301e0e0 | ||
|
|
c07065e457 | ||
|
|
b9e1afae17 | ||
|
|
549b4a9ebf | ||
|
|
fa9a3fb7c4 | ||
|
|
ccb15f278a | ||
|
|
f0fb4b8842 | ||
|
|
dacaf293b2 | ||
|
|
627d0e4e6f | ||
|
|
f32b9e26e0 | ||
|
|
db2b1a0a35 | ||
|
|
f734a60c62 | ||
|
|
9aa815c1ed | ||
|
|
e0772500c9 | ||
|
|
718f79775e | ||
|
|
4261c1768a | ||
|
|
01603185c3 | ||
|
|
f1adf24f3b | ||
|
|
856765a775 | ||
|
|
075da34cdb | ||
|
|
5ce543002d | ||
|
|
700b1c50af | ||
|
|
9e1c363a0a | ||
|
|
bbe3065f19 | ||
|
|
1716a0ebca | ||
|
|
ed66339e94 | ||
|
|
441774e61c | ||
|
|
c6fd14afd2 | ||
|
|
27ed9ca7fd | ||
|
|
50fda906bd | ||
|
|
6c7c60037b | ||
|
|
91a429ea77 | ||
|
|
2dbe01e90a | ||
|
|
3d7dd41223 | ||
|
|
91495c8595 | ||
|
|
c3e997168a | ||
|
|
c3e6f4f7b6 | ||
|
|
e600ec95ed | ||
|
|
cbd5853389 | ||
|
|
4e3a086993 | ||
|
|
5f23dfb034 | ||
|
|
99f59490a3 | ||
|
|
4a44b35ea5 | ||
|
|
47aafe9042 | ||
|
|
53d94a1d3d | ||
|
|
5dd3c7c8e8 | ||
|
|
0c2a13f27f | ||
|
|
da0d3133df | ||
|
|
7abd9a3db9 | ||
|
|
98b9f57e0e | ||
|
|
e064ccd585 | ||
|
|
b5a63ed3fc | ||
|
|
147edddb90 | ||
|
|
4b8a91fb8c | ||
|
|
4fb677ed23 | ||
|
|
4f0bd7f461 | ||
|
|
01eecdb698 | ||
|
|
5ca41a6fff | ||
|
|
e4f71420fc | ||
|
|
d5456ed1db | ||
|
|
e3335c5843 | ||
|
|
067e6bf93d | ||
|
|
13dc31dc1f | ||
|
|
7f6bf21270 | ||
|
|
d647258856 | ||
|
|
1eb7ed5e9c | ||
|
|
e78f1b05a7 | ||
|
|
e45140b7ea | ||
|
|
41747a7a04 | ||
|
|
e448430d8a | ||
|
|
394b1a1602 | ||
|
|
85abd5a8c3 | ||
|
|
b9e0eb5b76 | ||
|
|
a8eae73b66 | ||
|
|
9edd8d6f66 | ||
|
|
e5d883f97e |
@@ -0,0 +1,34 @@
|
||||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ node ]
|
||||
pull_request:
|
||||
branches: [ node ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [17.x, 18.x, 19.x, 20.x, 21.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
|
||||
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
|
||||
- run: npm install
|
||||
- run: npm update
|
||||
- run: jake dist
|
||||
- run: jake test-only
|
||||
@@ -17,3 +17,12 @@ Tests/Manual/**/*.xcodeproj
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.tm_properties
|
||||
*.idea
|
||||
node_modules
|
||||
*.vscode
|
||||
/dist/objective-j/objj-executable
|
||||
/dist/objective-j/package.json
|
||||
/dist/objective-j/lib
|
||||
/dist/cappuccino/package.json
|
||||
/dist/cappuccino/lib
|
||||
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
.DS_Store
|
||||
Frameworks
|
||||
!/dist/objective-j/Frameworks
|
||||
!/dist/cappuccino-j/Frameworks
|
||||
Build
|
||||
Demos
|
||||
./Aristo
|
||||
WebSite
|
||||
.push-package
|
||||
*.xcodeproj*
|
||||
*.xcodeproj/*.pbxuser
|
||||
*.xcodeproj/*.perspectivev3
|
||||
xcuserdata/
|
||||
!*.xcodeproj/project.pbxproj
|
||||
*.xCodeSupport/
|
||||
*.XcodeSupport/
|
||||
*XcodeSupport/
|
||||
Tests/Manual/**/*.xcodeproj
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.tm_properties
|
||||
*.idea
|
||||
node_modules
|
||||
*.vscode
|
||||
/dist/objective-j/objj-executable
|
||||
/dist/objective-j/package.json
|
||||
/dist/cappuccino/package.json
|
||||
|
||||
+4
-3
@@ -27,6 +27,7 @@
|
||||
@import "CPAccordionView.j"
|
||||
@import "CPAlert.j"
|
||||
@import "CPAnimation.j"
|
||||
@import "CPAnimationContext.j"
|
||||
@import "CPAppearance.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPArrayController.j"
|
||||
@@ -61,8 +62,6 @@
|
||||
@import "CPDocument.j"
|
||||
@import "CPDocumentController.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPFlashMovie.j"
|
||||
@import "CPFlashView.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPFontManager.j"
|
||||
@import "CPGradient.j"
|
||||
@@ -100,6 +99,7 @@
|
||||
@import "CPTabView.j"
|
||||
@import "CPText.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPTextView.j"
|
||||
@import "CPTokenField.j"
|
||||
@import "CPToolbar.j"
|
||||
@import "CPToolbarItem.j"
|
||||
@@ -107,10 +107,11 @@
|
||||
@import "CPTreeNode.j"
|
||||
@import "CPUserDefaultsController.j"
|
||||
@import "CPView.j"
|
||||
@import "CPViewAnimator.j"
|
||||
@import "CPViewAnimation.j"
|
||||
@import "CPViewController.j"
|
||||
@import "CPVisualEffectView.j"
|
||||
@import "CPWebView.j"
|
||||
@import "CPWindow.j"
|
||||
@import "CPWindowController.j"
|
||||
@import "CPWorkspace.j"
|
||||
@import "CPWorkspace.j"
|
||||
|
||||
@@ -132,7 +132,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
+ (CPApplication)sharedApplication
|
||||
{
|
||||
if (!CPApp)
|
||||
CPApp = [[CPApplication alloc] init];
|
||||
CPApp = [[self alloc] init];
|
||||
|
||||
return CPApp;
|
||||
}
|
||||
@@ -216,7 +216,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
}
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(applicationShouldTerminate:)])
|
||||
_implementedDelegateMethods |= CPApplicationDelegate_applicationShouldTerminate_
|
||||
_implementedDelegateMethods |= CPApplicationDelegate_applicationShouldTerminate_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(applicationShouldTerminateMessage:)])
|
||||
_implementedDelegateMethods |= CPApplicationDelegate_applicationShouldTerminateMessage_
|
||||
@@ -479,7 +479,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
[self _didResignActive];
|
||||
}
|
||||
|
||||
- (void)isActive
|
||||
- (BOOL)isActive
|
||||
{
|
||||
return _isActive;
|
||||
}
|
||||
@@ -628,11 +628,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
var theWindow = [anEvent window];
|
||||
|
||||
// Check if this is a candidate for key equivalent...
|
||||
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
|
||||
// The key equivalent was handled.
|
||||
return;
|
||||
|
||||
if ([anEvent type] == CPMouseMoved)
|
||||
{
|
||||
if (theWindow !== _lastMouseMoveWindow)
|
||||
@@ -669,6 +664,11 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
_eventListenerInsertionIndex = _eventListeners.length;
|
||||
|
||||
// Check if this is a candidate for key equivalent...
|
||||
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
|
||||
// The key equivalent was handled.
|
||||
return;
|
||||
|
||||
if (theWindow)
|
||||
[theWindow sendEvent:anEvent];
|
||||
}
|
||||
|
||||
+25
-26
@@ -219,17 +219,6 @@
|
||||
_clearsFilterPredicateOnInsertion = aFlag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Whether the receiver will always return the multiple values marker when multiple
|
||||
items are selected, even if the items have the same value.
|
||||
|
||||
@return BOOL YES if the receiver always uses the multiple values marker
|
||||
*/
|
||||
- (BOOL)alwaysUsesMultipleValuesMarker
|
||||
{
|
||||
return _alwaysUsesMultipleValuesMarker;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the receiver should always return the multiple values marker when multiple
|
||||
items are selected, even if the items have the same value.
|
||||
@@ -282,7 +271,7 @@
|
||||
if (_disableSetContent)
|
||||
return;
|
||||
|
||||
if (value === nil)
|
||||
if (value == nil)
|
||||
value = [];
|
||||
|
||||
if (![value isKindOfClass:[CPArray class]])
|
||||
@@ -308,27 +297,18 @@
|
||||
class.
|
||||
*/
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
[self willChangeValueForKey:@"filterPredicate"];
|
||||
|
||||
// Don't use [super setContent:] as that would fire the contentObject change.
|
||||
// We need to be in control of when notifications fire.
|
||||
// Note that if we have a contentArray binding, setting the content does /not/
|
||||
// cause a reverse binding set.
|
||||
_contentObject = value;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil)
|
||||
[self __setFilterPredicate:nil]; // Causes a _rearrangeObjects.
|
||||
else
|
||||
[self _rearrangeObjects];
|
||||
[self _rearrangeObjects];
|
||||
|
||||
if ([self preservesSelection])
|
||||
[self __setSelectedObjects:oldSelectedObjects];
|
||||
else
|
||||
[self __setSelectionIndexes:oldSelectionIndexes];
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -563,7 +543,7 @@
|
||||
*/
|
||||
- (BOOL)setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self _selectionWillChange]
|
||||
[self _selectionWillChange];
|
||||
var r = [self __setSelectionIndexes:indexes avoidEmpty:NO];
|
||||
[self _selectionDidChange];
|
||||
return r;
|
||||
@@ -629,6 +609,25 @@
|
||||
|
||||
return YES;
|
||||
}
|
||||
- (void)_selectionWillChange
|
||||
{
|
||||
// Push back all data from the dirty editors before it is too late.
|
||||
|
||||
var editorsCount = [_editors count];
|
||||
|
||||
while (editorsCount--)
|
||||
{
|
||||
var allBindings = [CPBinder allBindingsForObject:_editors[editorsCount]],
|
||||
allKeys = [allBindings allKeys],
|
||||
keysCount = allKeys.length;
|
||||
|
||||
while (keysCount--)
|
||||
[[allBindings objectForKey:allKeys[keysCount]] reverseSetValueFor:allKeys[keysCount]];
|
||||
}
|
||||
|
||||
[super _selectionWillChange];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns an array of the selected objects.
|
||||
@@ -774,7 +773,7 @@
|
||||
_filterPredicate = nil;
|
||||
[self _rearrangeObjects];
|
||||
}
|
||||
else if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
|
||||
else if (_filterPredicate == nil || [_filterPredicate evaluateWithObject:object])
|
||||
{
|
||||
// Insert directly into the array.
|
||||
var pos = [_arrangedObjects insertObject:object inArraySortedByDescriptors:_sortDescriptors];
|
||||
@@ -787,7 +786,7 @@
|
||||
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
|
||||
}
|
||||
/*
|
||||
else if (_filterPredicate !== nil)
|
||||
else if (_filterPredicate != nil)
|
||||
...
|
||||
// Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
|
||||
// not appear in arrangedObjects and we do not have to update at all.
|
||||
@@ -887,7 +886,7 @@
|
||||
|
||||
_disableSetContent = NO;
|
||||
|
||||
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
|
||||
if (_filterPredicate == nil || [_filterPredicate evaluateWithObject:object])
|
||||
{
|
||||
// selectionIndexes change notification will be fired as a result of the
|
||||
// content change. Don't fire manually.
|
||||
|
||||
@@ -231,7 +231,7 @@ var DefaultLineWidth = 1.0;
|
||||
/*!
|
||||
Cocoa compatibility.
|
||||
*/
|
||||
- (void)getLineDash:(CPArrayRef)patternRef count:(NSInteger)count phase:(CGFloatRef)phaseRef
|
||||
- (void)getLineDash:(CPArrayRef)patternRef count:(CPInteger)count phase:(CGFloatRef)phaseRef
|
||||
{
|
||||
return [self getLineDash:patternRef phase:phaseRef];
|
||||
}
|
||||
@@ -250,7 +250,7 @@ var DefaultLineWidth = 1.0;
|
||||
/*!
|
||||
Cocoa compatibility.
|
||||
*/
|
||||
- (void)setLineDash:(CPArray)aPattern count:(NSInteger)count phase:(CGFloat)aPhase
|
||||
- (void)setLineDash:(CPArray)aPattern count:(CPInteger)count phase:(CGFloat)aPhase
|
||||
{
|
||||
[self setLineDash:aPattern phase:aPhase];
|
||||
}
|
||||
|
||||
+368
-70
@@ -22,13 +22,14 @@
|
||||
|
||||
@import "CPTextField.j"
|
||||
@import "CPView.j"
|
||||
@import <Foundation/CPGeometry.j>
|
||||
|
||||
// CPBoxType
|
||||
@typedef CPBoxType
|
||||
CPBoxPrimary = 0;
|
||||
CPBoxSecondary = 1;
|
||||
CPBoxSecondary = 1; // Deprecated
|
||||
CPBoxSeparator = 2;
|
||||
CPBoxOldStyle = 3;
|
||||
CPBoxOldStyle = 3; // Deprecated
|
||||
CPBoxCustom = 4;
|
||||
|
||||
// CPBorderType
|
||||
@@ -58,12 +59,16 @@ CPBelowBottom = 6;
|
||||
@implementation CPBox : CPView
|
||||
{
|
||||
CPBoxType _boxType;
|
||||
CPBorderType _borderType;
|
||||
CPBorderType _borderType; // deprecated
|
||||
CPView _contentView;
|
||||
CPView _boxView; // needed for CSS theming, will be transparent for non CSS themes
|
||||
BOOL _transparent @accessors(getter=isTransparent);
|
||||
|
||||
CPString _title @accessors(getter=title);
|
||||
int _titlePosition @accessors(getter=titlePosition);
|
||||
CPString _title @accessors(getter=title);
|
||||
int _titlePosition @accessors(getter=titlePosition);
|
||||
CPTextField _titleView;
|
||||
|
||||
BOOL _cachedAutoresizesSubviews;
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
@@ -90,6 +95,14 @@ CPBelowBottom = 6;
|
||||
@"inner-shadow-size": 6.0,
|
||||
@"inner-shadow-color": [CPNull null],
|
||||
@"content-margin": CGSizeMakeZero(),
|
||||
@"title-font": [CPNull null],
|
||||
@"title-left-offset": 5.0,
|
||||
@"title-top-offset": 0.0,
|
||||
@"title-color": [CPNull null],
|
||||
@"nib2cib-adjustment-primary-frame": CGRectMake(4, -4, -8, -6),
|
||||
@"content-adjustment": CGRectMakeZero(),
|
||||
@"min-y-correction-no-title": 0,
|
||||
@"min-y-correction-title": 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,16 +127,26 @@ CPBelowBottom = 6;
|
||||
|
||||
if (self)
|
||||
{
|
||||
_borderType = CPBezelBorder;
|
||||
_borderType = CPGrooveBorder; // Was CPBezelBorder but Cocoa default is CPGrooveBorder
|
||||
_boxType = CPBoxPrimary;
|
||||
|
||||
_titlePosition = CPNoTitle;
|
||||
_titleView = [CPTextField labelWithTitle:@""];
|
||||
[_titleView setFont:[self titleFont]];
|
||||
[_titleView setTextColor:[self titleColor]];
|
||||
|
||||
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
|
||||
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
_contentView = [[CPView alloc] initWithFrame:[self bounds]];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[self setAutoresizesSubviews:YES];
|
||||
[self addSubview:_contentView];
|
||||
[self addSubview:_boxView];
|
||||
[_boxView setAutoresizesSubviews:YES];
|
||||
[_boxView addSubview:_contentView];
|
||||
|
||||
[self sizeToFit];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -155,6 +178,8 @@ CPBelowBottom = 6;
|
||||
*/
|
||||
- (CPBorderType)borderType
|
||||
{
|
||||
CPLog.warn("CPBox borderType is deprecated.");
|
||||
|
||||
return _borderType;
|
||||
}
|
||||
|
||||
@@ -177,7 +202,8 @@ CPBelowBottom = 6;
|
||||
return;
|
||||
|
||||
_borderType = aBorderType;
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -217,13 +243,46 @@ CPBelowBottom = 6;
|
||||
*/
|
||||
- (void)setBoxType:(CPBoxType)aBoxType
|
||||
{
|
||||
if ((aBoxType == CPBoxSecondary) || (aBoxType == CPBoxOldStyle))
|
||||
CPLog.warn("CPBox setBoxType: CPBoxSecondary and CPBoxOldStyle are deprecated.");
|
||||
|
||||
if (_boxType === aBoxType)
|
||||
return;
|
||||
|
||||
_boxType = aBoxType;
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
- (void)setTransparent:(BOOL)shouldBeTransparent
|
||||
{
|
||||
if (_transparent == shouldBeTransparent)
|
||||
return;
|
||||
|
||||
_transparent = shouldBeTransparent;
|
||||
|
||||
[self _manageTitlePositioning];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Style properties which override theme values
|
||||
/*!
|
||||
The borderColor, borderWidth, cornerRadius and fillColor properties for the receiver
|
||||
are only supported for boxes with boxType === CPBoxCustom and borderType === CPLineBorder.
|
||||
Boxes with the Primary boxType have fixed values which are defined by the system theme.
|
||||
Apple does support lineTypes of Groove and Bezel for boxes of type CPBoxCustom, CPBoxSecondary and CPBoxOldStyle,
|
||||
but they are deprecated as of macOS 10.15.
|
||||
|
||||
Cappuccino has supported these in the past but no longer does so - both to simplify CSS-based theming and
|
||||
to avoid the effort needed for supporting something which will be very short-lived.
|
||||
|
||||
These styles can be recreated as custom theme elements by developers, as needed.
|
||||
Additionally, boxes with boxType === CPBoxSeparator (horizontal and vertical lines) have never allowed changing these values.
|
||||
No warnings are generated for separator boxes.
|
||||
*/
|
||||
|
||||
// See discussion above.
|
||||
#pragma mark borderColor
|
||||
- (CPColor)borderColor
|
||||
{
|
||||
return [self valueForThemeAttribute:@"border-color"];
|
||||
@@ -231,12 +290,25 @@ CPBelowBottom = 6;
|
||||
|
||||
- (void)setBorderColor:(CPColor)color
|
||||
{
|
||||
if (_boxType === CPBoxSeparator)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
|
||||
{
|
||||
CPLog.warn("CPBox setBorderColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderColor. Property is ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ([color isEqual:[self borderColor]])
|
||||
return;
|
||||
|
||||
[self setValue:color forThemeAttribute:@"border-color"];
|
||||
}
|
||||
|
||||
// See discussion above.
|
||||
#pragma mark borderWidth
|
||||
- (float)borderWidth
|
||||
{
|
||||
return [self valueForThemeAttribute:@"border-width"];
|
||||
@@ -244,12 +316,25 @@ CPBelowBottom = 6;
|
||||
|
||||
- (void)setBorderWidth:(float)width
|
||||
{
|
||||
if (_boxType === CPBoxSeparator)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
|
||||
{
|
||||
CPLog.warn("CPBox setBorderWidth: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderWidth. Property is ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (width === [self borderWidth])
|
||||
return;
|
||||
|
||||
[self setValue:width forThemeAttribute:@"border-width"];
|
||||
}
|
||||
|
||||
// See discussion above.
|
||||
#pragma mark cornerRadius
|
||||
- (float)cornerRadius
|
||||
{
|
||||
return [self valueForThemeAttribute:@"corner-radius"];
|
||||
@@ -257,12 +342,25 @@ CPBelowBottom = 6;
|
||||
|
||||
- (void)setCornerRadius:(float)radius
|
||||
{
|
||||
if (_boxType === CPBoxSeparator)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
|
||||
{
|
||||
CPLog.warn("CPBox setCornerRadius: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setCornerRadius. Property is ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (radius === [self cornerRadius])
|
||||
return;
|
||||
|
||||
[self setValue:radius forThemeAttribute:@"corner-radius"];
|
||||
}
|
||||
|
||||
// See discussion above.
|
||||
#pragma mark fillColor
|
||||
- (CPColor)fillColor
|
||||
{
|
||||
return [self valueForThemeAttribute:@"background-color"];
|
||||
@@ -270,6 +368,17 @@ CPBelowBottom = 6;
|
||||
|
||||
- (void)setFillColor:(CPColor)color
|
||||
{
|
||||
if (_boxType === CPBoxSeparator)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
|
||||
{
|
||||
CPLog.warn("CPBox setFillColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setFillColor. Property is ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ([color isEqual:[self fillColor]])
|
||||
return;
|
||||
|
||||
@@ -286,21 +395,20 @@ CPBelowBottom = 6;
|
||||
if (aView === _contentView)
|
||||
return;
|
||||
|
||||
var borderWidth = [self borderWidth],
|
||||
contentMargin = [self valueForThemeAttribute:@"content-margin"];
|
||||
|
||||
[aView setFrame:CGRectInset([self bounds], contentMargin.width + borderWidth, contentMargin.height + borderWidth)];
|
||||
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
// A nil contentView is allowed (tested in Cocoa 2013-02-22).
|
||||
if (!aView)
|
||||
[_contentView removeFromSuperview];
|
||||
else if (_contentView)
|
||||
[self replaceSubview:_contentView with:aView];
|
||||
[_boxView replaceSubview:_contentView with:aView];
|
||||
else
|
||||
[self addSubview:aView];
|
||||
[_boxView addSubview:aView];
|
||||
|
||||
_contentView = aView;
|
||||
|
||||
[self sizeToFit];
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
- (CGSize)contentViewMargins
|
||||
@@ -320,9 +428,14 @@ CPBelowBottom = 6;
|
||||
{
|
||||
var offset = [self _titleHeightOffset],
|
||||
borderWidth = [self borderWidth],
|
||||
contentMargin = [self valueForThemeAttribute:@"content-margin"];
|
||||
contentMargin = [self valueForThemeAttribute:@"content-margin"],
|
||||
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
|
||||
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
|
||||
|
||||
[self setFrame:CGRectInset(aRect, -(contentMargin.width + borderWidth), -(contentMargin.height + offset[0] + borderWidth))];
|
||||
[self setFrame:CGRectMake(aRect.origin.x - contentAdjustment.origin.x - contentMargin.width + borderWidth,
|
||||
aRect.origin.y - contentAdjustment.origin.y - contentMargin.height + borderWidth - minYCorrection,
|
||||
aRect.size.width + 2 * contentMargin.width - contentAdjustment.size.width,
|
||||
aRect.size.height + 2 * contentMargin.height - contentAdjustment.size.height)];
|
||||
}
|
||||
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
@@ -347,14 +460,42 @@ CPBelowBottom = 6;
|
||||
|
||||
- (CPFont)titleFont
|
||||
{
|
||||
return [_titleView font];
|
||||
if ([self hasThemeAttribute:@"title-font"])
|
||||
return [self valueForThemeAttribute:@"title-font"];
|
||||
else
|
||||
return [_titleView font];
|
||||
}
|
||||
|
||||
- (void)setTitleFont:(CPFont)aFont
|
||||
{
|
||||
if ([aFont isEqual:[self titleFont]])
|
||||
return;
|
||||
|
||||
if ([self hasThemeAttribute:@"title-font"])
|
||||
[self setValue:aFont forThemeAttribute:@"title-font"];
|
||||
|
||||
[_titleView setFont:aFont];
|
||||
}
|
||||
|
||||
- (CPColor)titleColor
|
||||
{
|
||||
if ([self hasThemeAttribute:@"title-color"])
|
||||
return [self valueForThemeAttribute:@"title-color"];
|
||||
else
|
||||
return [_titleView textColor];
|
||||
}
|
||||
|
||||
- (void)setTitleColor:(CPColor)aColor
|
||||
{
|
||||
if ([aColor isEqual:[self titleColor]])
|
||||
return;
|
||||
|
||||
if ([self hasThemeAttribute:@"title-color"])
|
||||
[self setValue:aColor forThemeAttribute:@"title-color"];
|
||||
|
||||
[_titleView setTextColor:aColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the text field used to display the receiver's title.
|
||||
|
||||
@@ -365,25 +506,39 @@ CPBelowBottom = 6;
|
||||
return _titleView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the rectangle in which the receiver’s title is drawn.
|
||||
*/
|
||||
- (CGRect)titleRect
|
||||
{
|
||||
return [_titleView frame];
|
||||
}
|
||||
|
||||
- (void)_manageTitlePositioning
|
||||
{
|
||||
if (_titlePosition == CPNoTitle)
|
||||
if ((_titlePosition == CPNoTitle) || _transparent)
|
||||
{
|
||||
[_titleView removeFromSuperview];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
if (_boxType !== CPBoxSeparator)
|
||||
[self sizeToFit];
|
||||
|
||||
[self refreshDisplay];
|
||||
return;
|
||||
}
|
||||
|
||||
[_titleView setStringValue:_title];
|
||||
[_titleView sizeToFit];
|
||||
[self addSubview:_titleView];
|
||||
|
||||
var titleLeftOffset = [self valueForThemeAttribute:@"title-left-offset"],
|
||||
titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
|
||||
|
||||
switch (_titlePosition)
|
||||
{
|
||||
case CPAtTop:
|
||||
case CPAboveTop:
|
||||
case CPBelowTop:
|
||||
[_titleView setFrameOrigin:CGPointMake(5.0, 0.0)];
|
||||
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, titleTopOffset)]; // FIXME: was 0.0
|
||||
[_titleView setAutoresizingMask:CPViewNotSizable];
|
||||
break;
|
||||
|
||||
@@ -391,39 +546,51 @@ CPBelowBottom = 6;
|
||||
case CPAtBottom:
|
||||
case CPBelowBottom:
|
||||
var h = [_titleView frameSize].height;
|
||||
[_titleView setFrameOrigin:CGPointMake(5.0, [self frameSize].height - h)];
|
||||
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, [self frameSize].height - h - titleTopOffset)];
|
||||
[_titleView setAutoresizingMask:CPViewMinYMargin];
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_transparent)
|
||||
[self addSubview:_titleView];
|
||||
|
||||
[self sizeToFit];
|
||||
[self setNeedsDisplay:YES];
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
- (void)sizeToFit
|
||||
{
|
||||
var contentFrame = [_contentView frame],
|
||||
offset = [self _titleHeightOffset],
|
||||
contentMargin = [self valueForThemeAttribute:@"content-margin"];
|
||||
var offset = [self _titleHeightOffset],
|
||||
size = [self frameSize];
|
||||
|
||||
if (!contentFrame)
|
||||
[_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
|
||||
|
||||
if (!_contentView)
|
||||
return;
|
||||
|
||||
[_contentView setFrameOrigin:CGPointMake(contentMargin.width, contentMargin.height + offset[1])];
|
||||
var boxSize = [_boxView frameSize],
|
||||
contentMargin = [self valueForThemeAttribute:@"content-margin"],
|
||||
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
|
||||
borderWidth = [self valueForThemeAttribute:@"border-width"],
|
||||
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
|
||||
|
||||
[_contentView setFrame:CGRectMake(contentAdjustment.origin.x + contentMargin.width - borderWidth,
|
||||
contentAdjustment.origin.y + contentMargin.height - borderWidth + minYCorrection,
|
||||
boxSize.width - 2 * contentMargin.width + contentAdjustment.size.width,
|
||||
boxSize.height - 2 * contentMargin.height + contentAdjustment.size.height)];
|
||||
}
|
||||
|
||||
- (float)_titleHeightOffset
|
||||
- (CPArray)_titleHeightOffset
|
||||
{
|
||||
if (_titlePosition == CPNoTitle)
|
||||
return [0.0, 0.0];
|
||||
var titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
|
||||
|
||||
switch (_titlePosition)
|
||||
{
|
||||
case CPAtTop:
|
||||
return [[_titleView frameSize].height, [_titleView frameSize].height];
|
||||
return [[_titleView frameSize].height + titleTopOffset, [_titleView frameSize].height + titleTopOffset];
|
||||
|
||||
case CPAtBottom:
|
||||
return [[_titleView frameSize].height, 0.0];
|
||||
return [[_titleView frameSize].height + titleTopOffset, 0.0];
|
||||
|
||||
default:
|
||||
return [0.0, 0.0];
|
||||
@@ -440,21 +607,24 @@ CPBelowBottom = 6;
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
|
||||
return;
|
||||
|
||||
var bounds = [self bounds];
|
||||
|
||||
switch (_boxType)
|
||||
if (_boxType === CPBoxSeparator)
|
||||
{
|
||||
case CPBoxSeparator:
|
||||
// NSBox does not include a horizontal flag for the separator type. We have to determine
|
||||
// the type of separator to draw by the width and height of the frame.
|
||||
if (CGRectGetWidth(bounds) === 5.0)
|
||||
return [self _drawVerticalSeparatorInRect:bounds];
|
||||
else if (CGRectGetHeight(bounds) === 5.0)
|
||||
return [self _drawHorizontalSeparatorInRect:bounds];
|
||||
|
||||
break;
|
||||
// NSBox does not include a horizontal flag for the separator type. We have to determine
|
||||
// the type of separator to draw by the width and height of the frame.
|
||||
if (CGRectGetWidth(bounds) === 5.0)
|
||||
return [self _drawVerticalSeparatorInRect:bounds];
|
||||
else if (CGRectGetHeight(bounds) === 5.0)
|
||||
return [self _drawHorizontalSeparatorInRect:bounds];
|
||||
}
|
||||
|
||||
if (_transparent)
|
||||
return;
|
||||
|
||||
if (_titlePosition == CPAtTop)
|
||||
{
|
||||
bounds.origin.y += [_titleView frameSize].height;
|
||||
@@ -475,9 +645,6 @@ CPBelowBottom = 6;
|
||||
switch (_borderType)
|
||||
{
|
||||
case CPBezelBorder:
|
||||
[self _drawBezelBorderInRect:bounds];
|
||||
break;
|
||||
|
||||
case CPGrooveBorder:
|
||||
case CPLineBorder:
|
||||
[self _drawLineBorderInRect:bounds];
|
||||
@@ -594,12 +761,83 @@ CPBelowBottom = 6;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPBox (CSSTheming)
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (![self isCSSBased] || (_boxType === CPBoxCustom))
|
||||
return;
|
||||
|
||||
var bounds = [self bounds];
|
||||
|
||||
if (_boxType === CPBoxSeparator)
|
||||
{
|
||||
if (bounds.size.width === 5.0)
|
||||
{
|
||||
// Vertical separator
|
||||
[_boxView setFrame:CGRectMake(2,0,1,bounds.size.height)];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Horizontal separator
|
||||
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
|
||||
}
|
||||
|
||||
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"border-color"]];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// All types of boxes (beside custom which is not covered here) always draw the same way, unless they are CPNoBorder.
|
||||
if ((_borderType !== CPNoBorder) && !_transparent)
|
||||
{
|
||||
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"background-color"]];
|
||||
return;
|
||||
}
|
||||
|
||||
// No border or transparent
|
||||
[_boxView setBackgroundColor:nil];
|
||||
}
|
||||
|
||||
- (BOOL)isCSSBased
|
||||
{
|
||||
return [[self theme] isCSSBased];
|
||||
}
|
||||
|
||||
- (void)refreshDisplay
|
||||
{
|
||||
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
|
||||
[self setNeedsLayout:YES];
|
||||
else
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setAutoresizesSubviews:(BOOL)flag
|
||||
{
|
||||
// CPBox should always resize its subviews, like in Cocoa, whatever is the corresponding flag set.
|
||||
// We have to keep the flag value as we could have to return it in -autoresizesSubview method.
|
||||
_cachedAutoresizesSubviews = !!flag;
|
||||
[super setAutoresizesSubviews:YES];
|
||||
}
|
||||
|
||||
- (BOOL)autoresizesSubview
|
||||
{
|
||||
return _cachedAutoresizesSubviews;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
var CPBoxTypeKey = @"CPBoxTypeKey",
|
||||
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
|
||||
CPBoxTitle = @"CPBoxTitle",
|
||||
CPBoxTitlePosition = @"CPBoxTitlePosition",
|
||||
CPBoxTitleView = @"CPBoxTitleView",
|
||||
CPBoxContentView = @"CPBoxContentView";
|
||||
CPBoxTitleKey = @"CPBoxTitleKey",
|
||||
CPBoxTitlePositionKey = @"CPBoxTitlePositionKey",
|
||||
CPBoxTitleViewKey = @"CPBoxTitleViewKey",
|
||||
CPBoxContentViewKey = @"CPBoxContentViewKey",
|
||||
CPBoxBoxViewKey = @"CPBoxBoxViewKey";
|
||||
|
||||
@implementation CPBox (CPCoding)
|
||||
|
||||
@@ -612,29 +850,56 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
|
||||
_boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
|
||||
_borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
|
||||
|
||||
_title = [aCoder decodeObjectForKey:CPBoxTitle];
|
||||
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition];
|
||||
_titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
|
||||
_title = [aCoder decodeObjectForKey:CPBoxTitleKey];
|
||||
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePositionKey];
|
||||
|
||||
if (_boxType != CPBoxSeparator)
|
||||
// Important : see comment on encodeWithCoder below
|
||||
|
||||
_boxView = [aCoder decodeObjectForKey:CPBoxBoxViewKey];
|
||||
|
||||
if (!_boxView)
|
||||
{
|
||||
// FIXME: we have a problem with CIB decoding here.
|
||||
// We should be able to simply add : _contentView = [self subviews][0]
|
||||
// but first box subview seems to be malformed (badly decoded).
|
||||
// For example, when deployed, this view doesn't have its _trackingAreas array initialized.
|
||||
// As a (temporary) workaround, we encode/decode the _contentView property. We then transfer the subview hierarchy
|
||||
// and replace the first (and only) box subview with this _contentView
|
||||
// We're coming from nib2cib.
|
||||
|
||||
_contentView = [aCoder decodeObjectForKey:CPBoxContentView] || [[CPView alloc] initWithFrame:[self bounds]];
|
||||
var malformedContentView = [self subviews][0];
|
||||
[_contentView setSubviews:[malformedContentView subviews]];
|
||||
[self replaceSubview:malformedContentView with:_contentView];
|
||||
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
|
||||
_titleView = [CPTextField labelWithTitle:_title];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're coming from elsewhere
|
||||
|
||||
_titleView = [aCoder decodeObjectForKey:CPBoxTitleViewKey];
|
||||
}
|
||||
|
||||
[self setAutoresizesSubviews:YES];
|
||||
_contentView = [aCoder decodeObjectForKey:CPBoxContentViewKey];
|
||||
|
||||
// FIXME: super-mega-hyper-trick : _contentView has a superview which is not normal !
|
||||
// FIXME: (see encodeWithCoder to understand why this is not possible)
|
||||
// FIXME: we fix this by hand. This is horrible so please find a structural solution !
|
||||
|
||||
if (_contentView)
|
||||
_contentView._superview = nil;
|
||||
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
[_boxView setAutoresizesSubviews:YES];
|
||||
[self setAutoresizesSubviews:YES];
|
||||
|
||||
if (_contentView)
|
||||
[_boxView setSubviews:@[_contentView]];
|
||||
|
||||
[self addSubview:_boxView];
|
||||
[self addSubview:_titleView];
|
||||
|
||||
if (_boxType === CPBoxSeparator)
|
||||
_titlePosition = CPNoTitle;
|
||||
|
||||
[_titleView setFont:[self titleFont]];
|
||||
[_titleView setTextColor:[self titleColor]];
|
||||
|
||||
[self _manageTitlePositioning];
|
||||
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -642,14 +907,47 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// We have to distinguish between 2 cases :
|
||||
// - we come from nib2cib
|
||||
// - we come from elsewhere
|
||||
//
|
||||
// When coming from nib2cib, we have no _boxView, _contentView, _titleView.
|
||||
// We fix _contentView to be the first (and only) subview.
|
||||
// They will have to be added on decoding.
|
||||
//
|
||||
// When coming from elsewhere, we remove _boxView (and thus _contentView) and _titleView
|
||||
// from the view hierarchy as we'll already encode them via variables.
|
||||
// They will be putted back during decoding. This way, we reduce the space and speed needed for coding.
|
||||
|
||||
var subviews = [self subviews];
|
||||
|
||||
if (!_boxView)
|
||||
{
|
||||
// We're coming from nib2cib.
|
||||
|
||||
_contentView = subviews[0];
|
||||
|
||||
[_contentView removeFromSuperview];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're coming from elsewhere.
|
||||
|
||||
[_boxView removeFromSuperview];
|
||||
[_titleView removeFromSuperview];
|
||||
}
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[self setSubviews:subviews];
|
||||
|
||||
[aCoder encodeInt:_boxType forKey:CPBoxTypeKey];
|
||||
[aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey];
|
||||
[aCoder encodeObject:_title forKey:CPBoxTitle];
|
||||
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition];
|
||||
[aCoder encodeObject:_titleView forKey:CPBoxTitleView];
|
||||
[aCoder encodeObject:_contentView forKey:CPBoxContentView];
|
||||
[aCoder encodeObject:_title forKey:CPBoxTitleKey];
|
||||
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePositionKey];
|
||||
[aCoder encodeConditionalObject:_contentView forKey:CPBoxContentViewKey];
|
||||
[aCoder encodeConditionalObject:_titleView forKey:CPBoxTitleViewKey];
|
||||
[aCoder encodeConditionalObject:_boxView forKey:CPBoxBoxViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+14
-19
@@ -42,7 +42,6 @@
|
||||
- (CPDragOperation)browser:(CPBrowser)browser validateDrop:(id)info proposedRow:(CPInteger)row column:(CPInteger)column dropOperation:(CPTableViewDropOperation)dropOperation;
|
||||
- (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)anItem;
|
||||
- (CPImage)browser:(CPBrowser)browser draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
|
||||
- (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)item;
|
||||
- (CPIndexSet)browser:(CPBrowser)browser selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes inColumn:(CPInteger)column;
|
||||
- (CPInteger)browser:(CPBrowser)browser numberOfChildrenOfItem:(id)item;
|
||||
- (CPView)browser:(CPBrowser)browser draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
|
||||
@@ -62,19 +61,18 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
CPBrowserDelegate_browser_shouldSelectRowIndexes_inColumn_ = 1 << 4,
|
||||
CPBrowserDelegate_browser_writeRowsWithIndexes_inColumn_toPasteboard_ = 1 << 5,
|
||||
CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_ = 1 << 6,
|
||||
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 7,
|
||||
CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 8,
|
||||
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 9,
|
||||
CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_ = 1 << 10,
|
||||
CPBrowserDelegate_browser_numberOfChildrenOfItem_ = 1 << 11,
|
||||
CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 12,
|
||||
CPBrowserDelegate_browser_child_ofItem_ = 1 << 13,
|
||||
CPBrowserDelegate_browser_objectValueForItem_ = 1 << 14,
|
||||
CPBrowserDelegate_rootItemForBrowser_ = 1 << 15,
|
||||
CPBrowserDelegate_browser_didChangeLastColumn_toColumn_ = 1 << 16,
|
||||
CPBrowserDelegate_browser_didResizeColumn_ = 1 << 17,
|
||||
CPBrowserDelegate_browserSelectionIsChanging_ = 1 << 18,
|
||||
CPBrowserDelegate_browserSelectionDidChange_ = 1 << 19;
|
||||
CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 7,
|
||||
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 8,
|
||||
CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_ = 1 << 9,
|
||||
CPBrowserDelegate_browser_numberOfChildrenOfItem_ = 1 << 10,
|
||||
CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 11,
|
||||
CPBrowserDelegate_browser_child_ofItem_ = 1 << 12,
|
||||
CPBrowserDelegate_browser_objectValueForItem_ = 1 << 13,
|
||||
CPBrowserDelegate_rootItemForBrowser_ = 1 << 14,
|
||||
CPBrowserDelegate_browser_didChangeLastColumn_toColumn_ = 1 << 15,
|
||||
CPBrowserDelegate_browser_didResizeColumn_ = 1 << 16,
|
||||
CPBrowserDelegate_browserSelectionIsChanging_ = 1 << 17,
|
||||
CPBrowserDelegate_browserSelectionDidChange_ = 1 << 18;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -211,9 +209,6 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:imageValueForItem:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_imageValueForItem_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_;
|
||||
|
||||
@@ -468,7 +463,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
- (id)itemAtRow:(CPInteger)row inColumn:(CPInteger)column
|
||||
{
|
||||
return [_tableDelegates[column] childAtIndex:row];
|
||||
return [_tableDelegates[column] childAtIndex:row] || nil;
|
||||
}
|
||||
|
||||
- (BOOL)isLeafItem:(id)item
|
||||
@@ -478,7 +473,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
- (id)parentForItemsInColumn:(CPInteger)column
|
||||
{
|
||||
return [_tableDelegates[column] _item];
|
||||
return [_tableDelegates[column] _item] || nil;
|
||||
}
|
||||
|
||||
- (CPSet)selectedItems
|
||||
|
||||
+247
-167
@@ -44,6 +44,7 @@ CPTexturedRoundedBezelStyle = 11; // Round Textured
|
||||
CPRoundRectBezelStyle = 12; // Round Rect
|
||||
CPRecessedBezelStyle = 13; // Recessed
|
||||
CPRoundedDisclosureBezelStyle = 14; // Disclosure
|
||||
CPInlineBezelStyle = 15; // Inline
|
||||
CPHUDBezelStyle = -1;
|
||||
|
||||
|
||||
@@ -72,14 +73,32 @@ CPPushInCellMask = CPPushInButtonMask;
|
||||
CPChangeGrayCellMask = CPGrayButtonMask;
|
||||
CPChangeBackgroundCellMask = CPBackgroundButtonMask;
|
||||
|
||||
CPButtonStateMixed = CPThemeState("mixed");
|
||||
CPButtonStateBezelStyleRounded = CPThemeState("rounded");
|
||||
CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect");
|
||||
CPButtonStateMixed = CPThemeState("mixed");
|
||||
CPButtonStateBezelStyleRounded = CPThemeState("rounded"); // IB style : Push
|
||||
CPButtonStateBezelStyleShadowlessSquare = CPThemeState("square"); // IB style : Square
|
||||
CPButtonStateBezelStyleSmallSquare = CPThemeState("gradient"); // IB style : Gradient
|
||||
CPButtonStateBezelStyleTexturedRounded = CPThemeState("textured-rounded"); // IB style : Textured rounded
|
||||
CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect"); // IB style : Round rect
|
||||
CPButtonStateBezelStyleRecessed = CPThemeState("recessed"); // IB style : Recessed
|
||||
CPButtonStateBezelStyleInline = CPThemeState("inline"); // IB style : Inline
|
||||
CPButtonStateBezelStyleRegularSquare = CPThemeState("bevel"); // IB style : Bevel
|
||||
CPButtonStateBezelStyleTextured = CPThemeState("textured"); // IB style : Textured
|
||||
CPButtonStateBezelStyleDisclosure = CPThemeState("disclosure"); // IB style : Disclosure triangle
|
||||
CPButtonStateBezelStyleRoundedDisclosure = CPThemeState("rounded-disclosure"); // IB style : Rounded disclosure
|
||||
|
||||
// add all future correspondance between bezel styles and theme state here.
|
||||
var CPButtonBezelStyleStateMap = @{
|
||||
CPRoundedBezelStyle: CPButtonStateBezelStyleRounded,
|
||||
CPRoundRectBezelStyle: CPButtonStateBezelStyleRoundRect,
|
||||
CPRoundedBezelStyle: CPButtonStateBezelStyleRounded,
|
||||
CPShadowlessSquareBezelStyle: CPButtonStateBezelStyleShadowlessSquare,
|
||||
CPSmallSquareBezelStyle: CPButtonStateBezelStyleSmallSquare,
|
||||
CPTexturedRoundedBezelStyle: CPButtonStateBezelStyleTexturedRounded,
|
||||
CPRoundRectBezelStyle: CPButtonStateBezelStyleRoundRect,
|
||||
CPRecessedBezelStyle: CPButtonStateBezelStyleRecessed,
|
||||
CPInlineBezelStyle: CPButtonStateBezelStyleInline,
|
||||
CPRegularSquareBezelStyle: CPButtonStateBezelStyleRegularSquare,
|
||||
CPTexturedSquareBezelStyle: CPButtonStateBezelStyleTextured,
|
||||
CPDisclosureBezelStyle: CPButtonStateBezelStyleDisclosure,
|
||||
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure
|
||||
};
|
||||
|
||||
/// @cond IGNORE
|
||||
@@ -108,6 +127,7 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
// NS-style Display Properties
|
||||
CPBezelStyle _bezelStyle;
|
||||
ThemeState _bezelState;
|
||||
|
||||
CPString _keyEquivalent;
|
||||
unsigned _keyEquivalentModifierMask;
|
||||
@@ -117,7 +137,7 @@ CPButtonImageOffset = 3.0;
|
||||
float _periodicDelay;
|
||||
float _periodicInterval;
|
||||
|
||||
BOOL _isTracking;
|
||||
BOOL _isHighlighted;
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
@@ -154,9 +174,17 @@ CPButtonImageOffset = 3.0;
|
||||
return @{
|
||||
@"image": [CPNull null],
|
||||
@"image-offset": 0.0,
|
||||
@"image-vertical-offset": 0.0,
|
||||
@"bezel-inset": CGInsetMakeZero(),
|
||||
@"content-inset": CGInsetMakeZero(),
|
||||
@"bezel-color": [CPNull null],
|
||||
@"image-position": CPImageLeft,
|
||||
@"vertical-alignment": CPCenterVerticalTextAlignment,
|
||||
@"alignment": CPCenterTextAlignment,
|
||||
@"image-scaling": CPImageScaleNone,
|
||||
@"invert-image": NO,
|
||||
@"invert-image-on-push": NO,
|
||||
@"image-color": [CPNull null] // If null, image color follows text color
|
||||
};
|
||||
}
|
||||
|
||||
@@ -171,12 +199,6 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
if (self)
|
||||
{
|
||||
// Should we instead override the defaults?
|
||||
[self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
|
||||
[self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
|
||||
[self setValue:CPImageLeft forThemeAttribute:@"image-position"];
|
||||
[self setValue:CPImageScaleNone forThemeAttribute:@"image-scaling"];
|
||||
|
||||
[self setBezelStyle:CPRoundRectBezelStyle];
|
||||
[self setBordered:YES];
|
||||
|
||||
@@ -258,30 +280,6 @@ CPButtonImageOffset = 3.0;
|
||||
anObjectValue = CPOnState;
|
||||
|
||||
[super setObjectValue:anObjectValue];
|
||||
|
||||
switch ([self objectValue])
|
||||
{
|
||||
case CPMixedState:
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
[self setThemeState:CPButtonStateMixed];
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
break;
|
||||
|
||||
case CPOnState:
|
||||
[self unsetThemeState:CPButtonStateMixed];
|
||||
[self setThemeState:CPThemeStateSelected];
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[self unsetThemeStates:[CPThemeStateSelected, CPButtonStateMixed, CPThemeStateHighlighted]];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -378,12 +376,21 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
[self setValue:anImage forThemeAttribute:@"image"];
|
||||
// This is needed when compiling themes
|
||||
if (!_bezelState)
|
||||
_bezelState = CPThemeStateNormal;
|
||||
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState];
|
||||
// if we omit this, images will disappear as soon as the button becomes disabled
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateDisabled)];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal];
|
||||
if (!_bezelState)
|
||||
_bezelState = CPThemeStateNormal;
|
||||
|
||||
return [self valueForThemeAttribute:@"image" inState:_bezelState];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -392,7 +399,8 @@ CPButtonImageOffset = 3.0;
|
||||
*/
|
||||
- (void)setAlternateImage:(CPImage)anImage
|
||||
{
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHighlighted)];
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateSelected)];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -400,7 +408,17 @@ CPButtonImageOffset = 3.0;
|
||||
*/
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image" inState:CPThemeStateHighlighted];
|
||||
return [self valueForThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateSelected)];
|
||||
}
|
||||
|
||||
- (void)setHoveredImage:(CPImage)anImage
|
||||
{
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHovered)];
|
||||
}
|
||||
|
||||
- (CPImage)hoveredImage
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHovered)];
|
||||
}
|
||||
|
||||
- (void)setImageOffset:(float)theImageOffset
|
||||
@@ -423,11 +441,6 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
_showsStateBy = aMask;
|
||||
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask) && [self state] != CPOffState)
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@@ -444,11 +457,8 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
_highlightsBy = aMask;
|
||||
|
||||
if ([self hasThemeState:CPThemeStateHighlighted])
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (CPInteger)highlightsBy
|
||||
@@ -533,9 +543,20 @@ CPButtonImageOffset = 3.0;
|
||||
_periodicInterval = anInterval;
|
||||
}
|
||||
|
||||
- (void)highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
if (_isHighlighted == shouldHighlight)
|
||||
return;
|
||||
|
||||
_isHighlighted = shouldHighlight;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if ([self isContinuous])
|
||||
if ([self isEnabled] && [self isContinuous])
|
||||
{
|
||||
_continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function()
|
||||
{
|
||||
@@ -555,46 +576,12 @@ CPButtonImageOffset = 3.0;
|
||||
[_target performSelector:_action withObject:self];
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
_isTracking = YES;
|
||||
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
|
||||
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self highlight:[self state] == CPOffState];
|
||||
else
|
||||
[self highlight:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self highlight:[self state] != CPOffState];
|
||||
else
|
||||
[self highlight:NO];
|
||||
}
|
||||
|
||||
return startedTracking;
|
||||
}
|
||||
|
||||
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
||||
{
|
||||
_isTracking = NO;
|
||||
|
||||
if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
|
||||
[self setNextState];
|
||||
else
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self highlight:[self state] != CPOffState];
|
||||
else
|
||||
[self highlight:NO];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
[self highlight:NO];
|
||||
[self invalidateTimers];
|
||||
}
|
||||
|
||||
@@ -615,7 +602,7 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
var contentInset = [self valueForThemeAttribute:@"content-inset" inState:[self _contentVisualState]];
|
||||
|
||||
return CGRectInsetByInset(bounds, contentInset);
|
||||
}
|
||||
@@ -642,7 +629,7 @@ CPButtonImageOffset = 3.0;
|
||||
size = [contentView frameSize];
|
||||
}
|
||||
else
|
||||
size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
size = [([self title] || " ") sizeWithFont:[self font]];
|
||||
|
||||
var contentInset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
@@ -698,86 +685,164 @@ CPButtonImageOffset = 3.0;
|
||||
return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (CPThemeState)_backgroundVisualState
|
||||
{
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state],
|
||||
buttonIsOn = (currentState !== CPOffState);
|
||||
|
||||
if (_isHighlighted && (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask)))
|
||||
visualState = visualState.and(CPThemeStateHighlighted);
|
||||
else
|
||||
visualState = visualState.without(CPThemeStateHighlighted);
|
||||
|
||||
if (buttonIsOn && (_showsStateBy & (CPPushInCellMask | CPChangeGrayCellMask)))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
else
|
||||
visualState = visualState.without(CPThemeStateSelected);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
// Note : We have to split content and image visual states as, for example, radio buttons don't follow push buttons behavior
|
||||
- (CPThemeState)_contentVisualState
|
||||
{
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state],
|
||||
buttonIsOn = (currentState !== CPOffState);
|
||||
|
||||
// If the button is pushed (_isHighlighted), always add the highlighted state
|
||||
if (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
|
||||
visualState = visualState.and(CPThemeStateHighlighted);
|
||||
else
|
||||
visualState = visualState.without(CPThemeStateHighlighted);
|
||||
|
||||
if (buttonIsOn && (_showsStateBy & CPContentsCellMask))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
else
|
||||
visualState = visualState.without(CPThemeStateSelected);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
- (CPThemeState)_imageVisualState
|
||||
{
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state],
|
||||
buttonIsOn = (currentState !== CPOffState);
|
||||
|
||||
// Remove highlighted & selected theme states
|
||||
visualState = visualState.without(CPThemeStateHighlighted);
|
||||
visualState = visualState.without(CPThemeStateSelected);
|
||||
|
||||
// Note : We have to deal with special case where button is ON, highlightsBy and showsStateBy use content, and button is pushed
|
||||
// BUT this should not be used for disclosure buttons !
|
||||
if (_isHighlighted && buttonIsOn && (_highlightsBy & CPContentsCellMask) && (_showsStateBy & CPContentsCellMask) && (_bezelStyle !== CPDisclosureBezelStyle))
|
||||
return visualState;
|
||||
|
||||
if (_isHighlighted && ((_highlightsBy & CPContentsCellMask) || (_highlightsBy & CPChangeGrayCellMask)))
|
||||
visualState = visualState.and(CPThemeStateHighlighted);
|
||||
|
||||
if (buttonIsOn && (_showsStateBy & CPContentsCellMask))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
- (CPString)_currentTitle
|
||||
{
|
||||
var buttonIsOn = ([self state] !== CPOffState);
|
||||
|
||||
// Note : We have to deal with special case where button is ON, highlightsBy and showsStateBy use content, and button is pushed
|
||||
if (_isHighlighted && buttonIsOn && (_highlightsBy & CPContentsCellMask) && (_showsStateBy & CPContentsCellMask))
|
||||
return _title;
|
||||
|
||||
else if (_alternateTitle && ((_isHighlighted && (_highlightsBy & CPContentsCellMask)) || (buttonIsOn && (_showsStateBy & CPContentsCellMask))))
|
||||
return _alternateTitle;
|
||||
|
||||
else
|
||||
return _title;
|
||||
}
|
||||
|
||||
- (CPImage)_currentImage
|
||||
{
|
||||
var visualState = [self _imageVisualState],
|
||||
currentImage = [self valueForThemeAttribute:@"image" inState:visualState],
|
||||
imageColor = [self valueForThemeAttribute:@"image-color" inState:visualState],
|
||||
buttonIsOn = ([self state] !== CPOffState);
|
||||
|
||||
if ([currentImage isMaterialIconImage])
|
||||
{
|
||||
if (([self valueForThemeAttribute:@"invert-image" inState:visualState] || ([self valueForThemeAttribute:@"invert-image-on-push" inState:visualState] && (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn)))))
|
||||
currentImage = [currentImage invertedImage];
|
||||
|
||||
else if (imageColor && [imageColor isKindOfClass:CPColor])
|
||||
// In some buttons, image color doesn't follow text color !
|
||||
currentImage = [currentImage imageVersionWithColor:imageColor];
|
||||
|
||||
else
|
||||
// By default, image color follows text color
|
||||
currentImage = [currentImage imageVersionWithColor:[self valueForThemeAttribute:@"text-color" inState:[self _contentVisualState]]];
|
||||
}
|
||||
|
||||
return currentImage;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"],
|
||||
|
||||
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"],
|
||||
|
||||
if (contentView)
|
||||
{
|
||||
var title = nil,
|
||||
image = nil;
|
||||
image = [self _currentImage],
|
||||
contentVisualState = [self _contentVisualState];
|
||||
|
||||
if (_isTracking)
|
||||
{
|
||||
if (_highlightsBy & CPContentsCellMask)
|
||||
{
|
||||
if (_showsStateBy & CPContentsCellMask)
|
||||
{
|
||||
title = ([self state] == CPOffState && _alternateTitle) ? _alternateTitle : _title;
|
||||
image = ([self state] == CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = [self alternateTitle];
|
||||
image = [self alternateImage];
|
||||
}
|
||||
}
|
||||
else if (_showsStateBy & CPContentsCellMask)
|
||||
{
|
||||
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
|
||||
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = _title;
|
||||
image = [self image];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_showsStateBy & CPContentsCellMask)
|
||||
{
|
||||
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
|
||||
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = _title;
|
||||
image = [self image];
|
||||
}
|
||||
}
|
||||
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color" inState:[self _backgroundVisualState]]];
|
||||
[contentView setText:[self _currentTitle]];
|
||||
[contentView setImage:image];
|
||||
|
||||
[contentView setText:title];
|
||||
[contentView setImage:image];
|
||||
[contentView setImageOffset:[self currentValueForThemeAttribute:@"image-offset"]];
|
||||
[contentView setImageOffset:[self valueForThemeAttribute:@"image-offset" inState:contentVisualState]];
|
||||
[contentView setImageVerticalOffset:[self valueForThemeAttribute:@"image-vertical-offset" inState:contentVisualState]];
|
||||
|
||||
[contentView setFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
|
||||
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
|
||||
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
|
||||
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
|
||||
[contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
|
||||
[contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
|
||||
[contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]];
|
||||
[contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]];
|
||||
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled];
|
||||
}
|
||||
[contentView setFont:[self font]];
|
||||
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:contentVisualState]];
|
||||
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:contentVisualState]];
|
||||
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:contentVisualState]];
|
||||
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:contentVisualState]];
|
||||
[contentView _setUsesSingleLineMode:YES];
|
||||
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:contentVisualState]];
|
||||
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:contentVisualState]];
|
||||
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position"]];
|
||||
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling"]];
|
||||
|
||||
// We don't automatically dim material icon images as the color is driven by the theme
|
||||
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled && ![image isMaterialIconImage]];
|
||||
}
|
||||
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (shouldBeBordered)
|
||||
{
|
||||
[self setThemeState:CPThemeStateBordered];
|
||||
|
||||
if (_bezelState)
|
||||
_bezelState = _bezelState.and(CPThemeStateBordered);
|
||||
else
|
||||
_bezelState = CPThemeStateBordered;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateBordered];
|
||||
|
||||
if (_bezelState)
|
||||
_bezelState = _bezelState.without(CPThemeStateBordered);
|
||||
else
|
||||
_bezelState = CPThemeStateNormal;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isBordered
|
||||
@@ -807,7 +872,7 @@ CPButtonImageOffset = 3.0;
|
||||
{
|
||||
var selfWindow = [self window];
|
||||
|
||||
if (selfWindow === aWindow || aWindow === nil)
|
||||
if (selfWindow === aWindow || aWindow == nil)
|
||||
return;
|
||||
|
||||
if ([selfWindow defaultButton] === self)
|
||||
@@ -873,17 +938,7 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
[self setState:[self nextState]];
|
||||
|
||||
var shouldHighlight = NO;
|
||||
|
||||
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
shouldHighlight = [self state] == CPOffState;
|
||||
else
|
||||
shouldHighlight = YES;
|
||||
}
|
||||
|
||||
[self highlight:shouldHighlight];
|
||||
[self highlight:YES];
|
||||
|
||||
try
|
||||
{
|
||||
@@ -895,8 +950,7 @@ CPButtonImageOffset = 3.0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (shouldHighlight)
|
||||
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
|
||||
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,6 +973,22 @@ CPButtonImageOffset = 3.0;
|
||||
[self setThemeState:newState];
|
||||
|
||||
_bezelStyle = aBezelStyle;
|
||||
|
||||
if (_bezelState && newState)
|
||||
{
|
||||
if (currentState)
|
||||
_bezelState =_bezelState.without(currentState);
|
||||
_bezelState = _bezelState.and(newState);
|
||||
}
|
||||
else
|
||||
_bezelState = newState || CPThemeStateNormal;
|
||||
|
||||
// For disclosure triangle and rounded, we have to move away from
|
||||
// what Xcode tells us as we implement visual behavior with images (so content)
|
||||
// and not background
|
||||
|
||||
if ((_bezelStyle === CPDisclosureBezelStyle) || (_bezelStyle === CPRoundedDisclosureBezelStyle))
|
||||
[self setShowsStateBy:CPContentsCellMask];
|
||||
}
|
||||
|
||||
- (unsigned)bezelStyle
|
||||
@@ -942,7 +1012,8 @@ var CPButtonImageKey = @"CPButtonImageKey",
|
||||
CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
|
||||
CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
|
||||
CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
|
||||
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey";
|
||||
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey",
|
||||
CPButtonBezelStyleKey = @"CPButtonBezelStyleKey";
|
||||
|
||||
@implementation CPButton (CPCoding)
|
||||
|
||||
@@ -991,6 +1062,12 @@ var CPButtonImageKey = @"CPButtonImageKey",
|
||||
|
||||
_keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonIsBorderedKey])
|
||||
[self setBordered:[aCoder decodeBoolForKey:CPButtonIsBorderedKey]];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonBezelStyleKey])
|
||||
[self setBezelStyle:[aCoder decodeIntForKey:CPButtonBezelStyleKey]];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
@@ -1025,6 +1102,9 @@ var CPButtonImageKey = @"CPButtonImageKey",
|
||||
|
||||
[aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey];
|
||||
[aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey];
|
||||
|
||||
[aCoder encodeBool:[self isBordered] forKey:CPButtonIsBorderedKey];
|
||||
[aCoder encodeInt: [self bezelStyle] forKey:CPButtonBezelStyleKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
[button addItemWithTitle:nil];
|
||||
[[button lastItem] setImage:image];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
[button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset"];
|
||||
[button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset" inState:CPPopUpButtonStatePullsDown];
|
||||
|
||||
[button setPullsDown:YES];
|
||||
|
||||
|
||||
+47
-17
@@ -94,23 +94,6 @@ CPCheckBoxImageOffset = 4.0;
|
||||
[self takeStateFromKeyPath:aKeyPath ofObjects:objects];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
[self highlight:YES];
|
||||
return startedTracking;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Override methods from CPButton
|
||||
@@ -135,6 +118,36 @@ CPCheckBoxImageOffset = 4.0;
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CPThemeState)_contentVisualState
|
||||
{
|
||||
// Note : Behavior differs from CPButton as title doesn't follow the highlightsBy content flag
|
||||
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state];
|
||||
|
||||
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
- (CPThemeState)_imageVisualState
|
||||
{
|
||||
// Note : Behavior differs from CPButton as we don't force "not selected" theme state
|
||||
// when button is highglighted and selected
|
||||
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state];
|
||||
|
||||
if (_isHighlighted && (_highlightsBy & CPContentsCellMask))
|
||||
visualState = visualState.and(CPThemeStateHighlighted);
|
||||
|
||||
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCheckBoxValueBinder : CPBinder
|
||||
@@ -163,3 +176,20 @@ CPCheckBoxImageOffset = 4.0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPCheckBox (TableDataView)
|
||||
|
||||
// We overide here _CPObject+Theme setValue:forThemeAttribute as CPCheckBox can be used as tableView data view
|
||||
// So, when outside a table data view, setValue:forThemeAttribute should store the value with the CPThemeStateNormal (default behavior)
|
||||
// When inside a table data view, it should store the value with the CPThemeStateTableDataView. If not, the value won't be used if the
|
||||
// theme defined a value for this attribute for state CPThemeStateTableDataView
|
||||
|
||||
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
|
||||
{
|
||||
[super setValue:aValue forThemeAttribute:aName];
|
||||
[super setValue:aValue forThemeAttribute:aName inState:CPThemeStateTableDataView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+44
-25
@@ -31,6 +31,7 @@
|
||||
@import "CPDragServer_Constants.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
|
||||
@class _CPCollectionViewDropIndicator
|
||||
|
||||
@@ -55,7 +56,7 @@ var CPCollectionViewDelegate_collectionView_acceptDrop_index_dropOperation_
|
||||
- (CPData)collectionView:(CPCollectionView)collectionView dataForItemsAtIndexes:(CPIndexSet)indices forType:(CPString)aType;
|
||||
- (CPDragOperation)collectionView:(CPCollectionView)collectionView validateDrop:(id)draggingInfo proposedIndex:(CPInteger)proposedDropIndex dropOperation:(CPCollectionViewDropOperation)proposedDropOperation;
|
||||
- (CPMenu)collectionView:(CPCollectionView)collectionView menuForItemAtIndex:(CPInteger)anIndex;
|
||||
- (CPView)collectionView:(CPCollectionView)collectionView dragginViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
|
||||
- (CPView)collectionView:(CPCollectionView)collectionView draggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
|
||||
- (void)collectionView:(CPCollectionView)collectionView didDoubleClickOnItemAtIndex:(int)index;
|
||||
|
||||
@end
|
||||
@@ -132,7 +133,6 @@ var HORIZONTAL_MARGIN = 2;
|
||||
CGSize _storedFrameSize;
|
||||
|
||||
BOOL _uniformSubviewsResizing @accessors(property=uniformSubviewsResizing);
|
||||
BOOL _lockResizing;
|
||||
|
||||
CPInteger _currentDropIndex;
|
||||
CPDragOperation _currentDragOperation;
|
||||
@@ -140,6 +140,14 @@ var HORIZONTAL_MARGIN = 2;
|
||||
_CPCollectionViewDropIndicator _dropView;
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding == CPContentBinding)
|
||||
return [_CPCollectionViewContentBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
@@ -182,7 +190,7 @@ var HORIZONTAL_MARGIN = 2;
|
||||
|
||||
_needsMinMaxItemSizeUpdate = YES;
|
||||
_uniformSubviewsResizing = NO;
|
||||
_lockResizing = NO;
|
||||
_inLiveResize = NO;
|
||||
|
||||
_currentDropIndex = -1;
|
||||
_currentDragOperation = CPDragOperationNone;
|
||||
@@ -384,14 +392,7 @@ var HORIZONTAL_MARGIN = 2;
|
||||
_isSelectable = isSelectable;
|
||||
|
||||
if (!_isSelectable)
|
||||
{
|
||||
var index = CPNotFound,
|
||||
itemCount = [_items count];
|
||||
|
||||
// Be wary of invalid selection ranges since setContent: does not clear selection indexes.
|
||||
while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < itemCount)
|
||||
[_items[index] setSelected:NO];
|
||||
}
|
||||
[self _applySelectionToItems:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -445,22 +446,15 @@ var HORIZONTAL_MARGIN = 2;
|
||||
{
|
||||
if (!anIndexSet)
|
||||
anIndexSet = [CPIndexSet indexSet];
|
||||
|
||||
if (!_isSelectable || [_selectionIndexes isEqual:anIndexSet])
|
||||
return;
|
||||
|
||||
var index = CPNotFound,
|
||||
itemCount = [_items count];
|
||||
|
||||
// Be wary of invalid selection ranges since setContent: does not clear selection indexes.
|
||||
while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound && index < itemCount)
|
||||
[_items[index] setSelected:NO];
|
||||
[self _applySelectionToItems:NO];
|
||||
|
||||
_selectionIndexes = anIndexSet;
|
||||
|
||||
var index = CPNotFound;
|
||||
|
||||
while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound)
|
||||
[_items[index] setSelected:YES];
|
||||
[self _applySelectionToItems:YES];
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
|
||||
[[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"];
|
||||
@@ -525,14 +519,14 @@ var HORIZONTAL_MARGIN = 2;
|
||||
|
||||
- (void)resizeWithOldSuperviewSize:(CGSize)oldBoundsSize
|
||||
{
|
||||
if (_lockResizing)
|
||||
if (_inLiveResize)
|
||||
return;
|
||||
|
||||
_lockResizing = YES;
|
||||
_inLiveResize = YES;
|
||||
|
||||
[self tile];
|
||||
|
||||
_lockResizing = NO;
|
||||
_inLiveResize = NO;
|
||||
}
|
||||
|
||||
- (void)tile
|
||||
@@ -745,7 +739,7 @@ var HORIZONTAL_MARGIN = 2;
|
||||
*/
|
||||
- (void)setMinItemSize:(CGSize)aSize
|
||||
{
|
||||
if (aSize === nil || aSize === undefined)
|
||||
if (aSize == nil)
|
||||
[CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
|
||||
|
||||
if (CGSizeEqualToSize(_minItemSize, aSize))
|
||||
@@ -972,6 +966,20 @@ var HORIZONTAL_MARGIN = 2;
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (void)_applySelectionToItems:(BOOL)select
|
||||
{
|
||||
var numberOfItems = [_items count];
|
||||
|
||||
[_selectionIndexes enumerateIndexesUsingBlock:function(idx, stop)
|
||||
{
|
||||
if (idx < numberOfItems)
|
||||
[[_items objectAtIndex:idx] setSelected:select];
|
||||
else {
|
||||
stop(YES);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPCollectionView (DragAndDrop)
|
||||
@@ -1586,3 +1594,14 @@ var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeK
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCollectionViewContentBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setContent:aValue];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+251
-8
@@ -87,7 +87,7 @@ var cachedBlackColor,
|
||||
CPImage _patternImage;
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
@global document
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Theming
|
||||
@@ -100,8 +100,11 @@ var cachedBlackColor,
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"alternate-selected-control-color": [CPNull null],
|
||||
@"secondary-selected-control-color" : [CPNull null]
|
||||
@"alternate-selected-control-color": [CPNull null],
|
||||
@"secondary-selected-control-color": [CPNull null],
|
||||
@"selected-text-background-color": [CPNull null],
|
||||
@"selected-text-inactive-background-color": [CPNull null],
|
||||
@"css-based": NO
|
||||
};
|
||||
}
|
||||
|
||||
@@ -264,7 +267,7 @@ var cachedBlackColor,
|
||||
|
||||
@return an initialized RGB color
|
||||
*/
|
||||
+ (CPColor)colorWithHexString:(string)hex
|
||||
+ (CPColor)colorWithHexString:(CPString)hex
|
||||
{
|
||||
var rgba = hexToRGB(hex);
|
||||
return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
|
||||
@@ -498,6 +501,16 @@ var cachedBlackColor,
|
||||
return [[CPColor alloc] _initWithCSSString: aString];
|
||||
}
|
||||
|
||||
+ (CPColor)selectedTextBackgroundColor
|
||||
{
|
||||
return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-background-color"] || [CPColor colorWithHexString:"99CCFF"];
|
||||
}
|
||||
|
||||
+ (CPColor)_selectedTextBackgroundColorUnfocussed
|
||||
{
|
||||
return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-inactive-background-color"] || [CPColor colorWithHexString:"CCCCCC"];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithCSSString:(CPString)aString
|
||||
{
|
||||
@@ -548,7 +561,11 @@ var cachedBlackColor,
|
||||
|
||||
- (void)_initCSSStringFromComponents
|
||||
{
|
||||
var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
|
||||
// Fix to avoid a problem when compiling a theme (missing the alpha component as theme compiler doesn't have CSS rgba capability)
|
||||
var hasAlpha = YES;
|
||||
#if PLATFORM(DOM)
|
||||
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
|
||||
#endif
|
||||
|
||||
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
|
||||
parseInt(_components[0] * 255.0) + ", " +
|
||||
@@ -787,6 +804,9 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
|
||||
var description = [super description],
|
||||
patternImage = [self patternImage];
|
||||
|
||||
if ([self isCSSBased])
|
||||
return description + "\n" + [self cssDictionary]+ "\nBefore:\n" + [self cssBeforeDictionary] + "\nAfter:\n" + [self cssAfterDictionary];
|
||||
|
||||
if (!patternImage)
|
||||
return description + " " + [self cssString];
|
||||
|
||||
@@ -864,9 +884,221 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CSS Theming
|
||||
|
||||
// The code below adds support for CSS theming with 100% compatibility with current theming system.
|
||||
// The idea is to extend CPColor (and CPImage) with CSS components and adapt low level UI components to
|
||||
// support this new kind of CPColor/CPImage. See CPImageView, CPView and _CPImageAndTextView.
|
||||
//
|
||||
// To create a CPColor that uses CSS, simply use the new class method :
|
||||
// + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
|
||||
// where beforeDictionary & afterDictionary are related to ::before & ::after pseudo-elements.
|
||||
// If you don't need them, just use the simplified class method :
|
||||
// + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary
|
||||
//
|
||||
// Example :
|
||||
// buttonCssColor = [CPColor colorWithCSSDictionary:@{
|
||||
// @"background-color": A3ColorBackgroundWhite,
|
||||
// @"border-color": A3ColorActiveBorder,
|
||||
// @"border-style": @"solid",
|
||||
// @"border-width": @"1px",
|
||||
// @"border-radius": @"3px",
|
||||
// @"box-sizing": @"border-box"
|
||||
// }
|
||||
// beforeDictionary:@{
|
||||
// @"background-color": @"rgb(225,225,225)",
|
||||
// @"bottom": @"3px",
|
||||
// @"content": @"''",
|
||||
// @"position": @"absolute",
|
||||
// @"right": @"21px",
|
||||
// @"top": @"3px",
|
||||
// @"width": @"1px"
|
||||
// }
|
||||
// afterDictionary:@{
|
||||
// @"content": @"''",
|
||||
// @"bottom": @"3px",
|
||||
// @"right": @"6px",
|
||||
// @"top": @"1px",
|
||||
// @"position": @"absolute",
|
||||
// @"height": @"14px",
|
||||
// @"width": @"9px",
|
||||
// @"margin": @"2px 0px 2px 0px",
|
||||
// @"background-image": @"url(%%packed.png)",
|
||||
// @"background-position": @"0px -64px",
|
||||
// @"background-repeat": @"no-repeat",
|
||||
// @"background-size": @"100px 400px"
|
||||
// }];
|
||||
//
|
||||
// Remark : Please note the special URL of the background image used in this example : url(%%packed.png)
|
||||
// During theme loading, "%%" will be replaced by the path to the theme blend resources folder.
|
||||
// Typically, a CSS theme will use some (rare) images all packed together in a single image resource (see packed.png in Aristo3 theme)
|
||||
//
|
||||
// Also, please note that if you don't use one of the CSS components, you can either set it to nil (best solution) or to an empty dictionary, like :
|
||||
// aCssColor = [CPColor colorWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:@{ ... }];
|
||||
//
|
||||
// You can use -(BOOL)isCSSBased to determine how to cope with it in your code.
|
||||
// -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use.
|
||||
//
|
||||
// Remark : +(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement
|
||||
// CSS theme support.
|
||||
|
||||
@implementation CPColor (CSSTheming)
|
||||
{
|
||||
CPDictionary _cssDictionary @accessors(property=cssDictionary);
|
||||
CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary);
|
||||
CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary);
|
||||
}
|
||||
|
||||
+ (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary
|
||||
{
|
||||
return [[CPColor alloc] _initWithCSSDictionary:aDictionary beforeDictionary:nil afterDictionary:nil];
|
||||
}
|
||||
|
||||
+ (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
|
||||
{
|
||||
return [[CPColor alloc] _initWithCSSDictionary:aDictionary beforeDictionary:beforeDictionary afterDictionary:afterDictionary];
|
||||
}
|
||||
|
||||
- (id)_initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_cssDictionary = aDictionary;
|
||||
_cssBeforeDictionary = beforeDictionary;
|
||||
_cssAfterDictionary = afterDictionary;
|
||||
_components = [0.0, 0.0, 0.0, 1.0];
|
||||
|
||||
_theme = [CPTheme defaultTheme];
|
||||
_themeState = CPThemeStateNormal;
|
||||
[self _loadThemeAttributes];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isCSSBased
|
||||
{
|
||||
return !!(_cssDictionary || _cssBeforeDictionary || _cssAfterDictionary);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSDictionary
|
||||
{
|
||||
return ([_cssDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSBeforeDictionary
|
||||
{
|
||||
return ([_cssBeforeDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSAfterDictionary
|
||||
{
|
||||
return ([_cssAfterDictionary count] > 0);
|
||||
}
|
||||
|
||||
+ (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var aPreviousState = @deref(aPreviousStateRef);
|
||||
|
||||
for (var i = 0, count = aPreviousState.length; i < count; i++)
|
||||
aDOMElement.style[aPreviousState[i][0]] = aPreviousState[i][1];
|
||||
|
||||
@deref(aPreviousStateRef) = @[];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (DOMElement)applyCSSColorForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var aPreviousState = @deref(aPreviousStateRef);
|
||||
|
||||
[_cssDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[aPreviousState addObject:@[aKey, aDOMElement.style[aKey]]];
|
||||
aDOMElement.style[aKey] = anObject;
|
||||
}];
|
||||
|
||||
if ([self hasCSSBeforeDictionary] || [self hasCSSAfterDictionary])
|
||||
{
|
||||
// We need to create a unique class name
|
||||
|
||||
var styleClassName = @".CP" + [aView UID],
|
||||
styleContent = @"";
|
||||
|
||||
if ([self hasCSSBeforeDictionary])
|
||||
{
|
||||
styleContent += styleClassName + @"::before { ";
|
||||
|
||||
[_cssBeforeDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
styleContent += aKey + ": " + anObject + "; ";
|
||||
}];
|
||||
|
||||
styleContent += "} ";
|
||||
}
|
||||
|
||||
if ([self hasCSSAfterDictionary])
|
||||
{
|
||||
styleContent += styleClassName + @"::after { ";
|
||||
|
||||
[_cssAfterDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
styleContent += aKey + ": " + anObject + "; ";
|
||||
}];
|
||||
|
||||
styleContent += "} ";
|
||||
}
|
||||
|
||||
var styleDescription = document.createTextNode(styleContent);
|
||||
|
||||
if (!aStyleNode)
|
||||
{
|
||||
aStyleNode = document.createElement("style");
|
||||
|
||||
aView._DOMElement.insertBefore(aStyleNode, aView._DOMElement.firstChild);
|
||||
|
||||
aStyleNode.appendChild(styleDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
|
||||
}
|
||||
|
||||
[aView setDOMClassName:@"CP"+[aView UID]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// no before/after so remove aStyleNode if existing
|
||||
|
||||
if (aStyleNode)
|
||||
{
|
||||
aView._DOMElement.removeChild(aStyleNode);
|
||||
aStyleNode = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Return actualised values
|
||||
|
||||
@deref(aPreviousStateRef) = aPreviousState;
|
||||
|
||||
return aStyleNode;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/// @cond IGNORE
|
||||
var CPColorComponentsKey = @"CPColorComponentsKey",
|
||||
CPColorPatternImageKey = @"CPColorPatternImageKey";
|
||||
var CPColorComponentsKey = @"CPColorComponentsKey",
|
||||
CPColorPatternImageKey = @"CPColorPatternImageKey",
|
||||
CPColorCssDictionaryKey = @"CPColorCssDictionaryKey",
|
||||
CPColorCssBeforeDictionaryKey = @"CPColorCssBeforeDictionaryKey",
|
||||
CPColorCssAfterDictionaryKey = @"CPColorCssAfterDictionaryKey";
|
||||
/// @endcond
|
||||
|
||||
@implementation CPColor (CPCoding)
|
||||
@@ -879,6 +1111,10 @@ var CPColorComponentsKey = @"CPColorComponentsKey",
|
||||
{
|
||||
if ([aCoder containsValueForKey:CPColorPatternImageKey])
|
||||
self = [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
|
||||
else if ([aCoder containsValueForKey:CPColorCssDictionaryKey])
|
||||
self = [self _initWithCSSDictionary:[aCoder decodeObjectForKey:CPColorCssDictionaryKey]
|
||||
beforeDictionary:[aCoder decodeObjectForKey:CPColorCssBeforeDictionaryKey]
|
||||
afterDictionary:[aCoder decodeObjectForKey:CPColorCssAfterDictionaryKey]];
|
||||
else
|
||||
self = [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
|
||||
|
||||
@@ -895,6 +1131,12 @@ var CPColorComponentsKey = @"CPColorComponentsKey",
|
||||
{
|
||||
if (_patternImage)
|
||||
[aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
|
||||
else if (_cssDictionary)
|
||||
{
|
||||
[aCoder encodeObject:_cssDictionary forKey:CPColorCssDictionaryKey];
|
||||
[aCoder encodeObject:_cssBeforeDictionary forKey:CPColorCssBeforeDictionaryKey];
|
||||
[aCoder encodeObject:_cssAfterDictionary forKey:CPColorCssAfterDictionaryKey];
|
||||
}
|
||||
else
|
||||
[aCoder encodeObject:_components forKey:CPColorComponentsKey];
|
||||
|
||||
@@ -1235,7 +1477,8 @@ var patternColorsFromPattern = function(pattern, attributes, imageFactory)
|
||||
bottomHeight,
|
||||
centerWidthHeight,
|
||||
centerIsNil,
|
||||
numParts;
|
||||
numParts,
|
||||
isVertical;
|
||||
|
||||
// positions are mandatory
|
||||
if (pattern.indexOf("{position}") < 0)
|
||||
|
||||
@@ -483,7 +483,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
];
|
||||
}
|
||||
|
||||
var cookieValue = eval(cookieValue);
|
||||
var cookieValue = JSON.parse(cookieValue);
|
||||
|
||||
return [cookieValue arrayByApplyingBlock:function(value)
|
||||
{
|
||||
|
||||
@@ -125,7 +125,8 @@
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame:CGRectMake(0, (aFrame.size.height - 34), aFrame.size.width, 15)];
|
||||
|
||||
[_brightnessSlider setValue:15.0 forThemeAttribute:@"track-width"];
|
||||
[_brightnessSlider setValue:[CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"]]] forThemeAttribute:@"track-color"];
|
||||
var brightnessImage = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"] size:CGSizeMake(272, 20)];
|
||||
[_brightnessSlider setValue:[CPColor colorWithPatternImage:brightnessImage] forThemeAttribute:@"track-color"];
|
||||
|
||||
[_brightnessSlider setMinValue:0.0];
|
||||
[_brightnessSlider setMaxValue:100.0];
|
||||
@@ -246,7 +247,7 @@
|
||||
_blackWheelImage = new Image();
|
||||
_blackWheelImage.src = path;
|
||||
_blackWheelImage.style.opacity = "0";
|
||||
_blackWheelImage.style.filter = "alpha(opacity=0)"
|
||||
_blackWheelImage.style.filter = "alpha(opacity=0)";
|
||||
_blackWheelImage.style.position = "absolute";
|
||||
|
||||
_DOMElement.appendChild(_wheelImage);
|
||||
|
||||
@@ -255,6 +255,8 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
|
||||
var colorPanel = [CPColorPanel sharedColorPanel];
|
||||
|
||||
[colorPanel setPlatformWindow:[[self window] platformWindow]];
|
||||
|
||||
[colorPanel setColor:_color];
|
||||
[colorPanel orderFront:self];
|
||||
}
|
||||
|
||||
+1
-1
@@ -564,7 +564,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
|
||||
var selectedStringValue = [_listDelegate selectedStringValue];
|
||||
|
||||
if (selectedStringValue === nil)
|
||||
if (selectedStringValue == nil)
|
||||
return NO;
|
||||
else
|
||||
_selectedStringValue = selectedStringValue;
|
||||
|
||||
+77
-16
@@ -26,11 +26,13 @@
|
||||
|
||||
// Browser Engines
|
||||
CPUnknownBrowserEngine = 0;
|
||||
CPGeckoBrowserEngine = 1;
|
||||
CPInternetExplorerBrowserEngine = 2;
|
||||
CPKHTMLBrowserEngine = 3;
|
||||
CPOperaBrowserEngine = 4;
|
||||
CPWebKitBrowserEngine = 5;
|
||||
CPGeckoBrowserEngine = 1 << 0;
|
||||
CPInternetExplorerBrowserEngine = 1 << 1;
|
||||
CPKHTMLBrowserEngine = 1 << 2;
|
||||
CPOperaBrowserEngine = 1 << 3;
|
||||
CPWebKitBrowserEngine = 1 << 4; // Safari + Chrome
|
||||
CPBlinkBrowserEngine = 1 << 5; // Recent Chrome
|
||||
CPEdgeBrowserEngine = 1 << 6;
|
||||
|
||||
// Operating Systems
|
||||
CPMacOperatingSystem = 0;
|
||||
@@ -80,18 +82,28 @@ CPFileAPIFeature = 31;
|
||||
|
||||
CPAltEnterTextAreaFeature = 32;
|
||||
|
||||
CPCSSAnimationFeature = 33;
|
||||
|
||||
|
||||
CPBackspaceTriggersPageBack = 34;
|
||||
/*
|
||||
When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
|
||||
*/
|
||||
*/
|
||||
CPCanvasParentDrawErrorsOnMovementBug = 1 << 0;
|
||||
|
||||
// The paste event is only sent if an input or textarea has focus.
|
||||
CPJavaScriptPasteRequiresEditableTarget = 1 << 1;
|
||||
CPJavaScriptPasteRequiresEditableTarget = 1 << 1;
|
||||
// Redirecting the focus of the browser on keydown to an input for Cmd-V or Ctrl-V makes the paste fail.
|
||||
CPJavaScriptPasteCantRefocus = 1 << 2;
|
||||
|
||||
/*
|
||||
Safari calculates incorrect text size unless you set the canvas font even if it is already set
|
||||
You can see the bug after disabling the workaround and opening any panel while typing.
|
||||
You can use the font panel in the manual test for CPTextView.
|
||||
Look out for a displaced cursor, i.e. after typing letters of small width, such as the 'i'.
|
||||
https://bugs.webkit.org/show_bug.cgi?id=150224
|
||||
*/
|
||||
CPTextSizingAlwaysNeedsSetFontBug = 1 << 3;
|
||||
|
||||
|
||||
var USER_AGENT = "",
|
||||
PLATFORM_ENGINE = CPUnknownBrowserEngine,
|
||||
@@ -110,15 +122,15 @@ if (typeof window !== "undefined" && typeof window.navigator !== "undefined")
|
||||
// Opera
|
||||
if (typeof window !== "undefined" && window.opera)
|
||||
{
|
||||
PLATFORM_ENGINE = CPOperaBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPOperaBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
|
||||
}
|
||||
|
||||
// Internet Explorer
|
||||
else if (typeof window !== "undefined" && window.attachEvent) // Must follow Opera check.
|
||||
else if (typeof window !== "undefined" && (window.attachEvent || (!(window.ActiveXObject) && "ActiveXObject" in window))) // Must follow Opera check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPInternetExplorerBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPInternetExplorerBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with IE (no known independent tests)
|
||||
PLATFORM_FEATURES[CPVMLFeature] = YES;
|
||||
@@ -136,10 +148,32 @@ else if (typeof window !== "undefined" && window.attachEvent) // Must follow Ope
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
|
||||
}
|
||||
|
||||
// WebKit
|
||||
// Edge
|
||||
else if (USER_AGENT.indexOf("Edge/") != -1)
|
||||
{
|
||||
PLATFORM_ENGINE |= CPEdgeBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
|
||||
PLATFORM_FEATURES[CPHTMLContentEditableFeature] = YES;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = NO;
|
||||
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;
|
||||
|
||||
var versionStart = USER_AGENT.indexOf("Edge/") + "Edge/".length,
|
||||
versionEnd = USER_AGENT.indexOf(" ", versionStart),
|
||||
versionString = USER_AGENT.substring(versionStart, versionEnd),
|
||||
versionDivision = versionString.indexOf('.'),
|
||||
majorVersion = parseInt(versionString.substring(0, versionDivision)),
|
||||
minorVersion = parseInt(versionString.substr(versionDivision + 1));
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
|
||||
}
|
||||
|
||||
// Safari + Chrome (WebKit and Blink)
|
||||
else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
{
|
||||
PLATFORM_ENGINE = CPWebKitBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPWebKitBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with WebKit (no known independent tests)
|
||||
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
|
||||
@@ -179,7 +213,10 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget;
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=39689
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteCantRefocus;
|
||||
PLATFORM_BUGS |= CPTextSizingAlwaysNeedsSetFontBug;
|
||||
}
|
||||
else if ((window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window)
|
||||
PLATFORM_ENGINE |= CPBlinkBrowserEngine;
|
||||
|
||||
// Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter.
|
||||
if (majorVersion > 533)
|
||||
@@ -189,15 +226,16 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
// KHTML
|
||||
else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPKHTMLBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPKHTMLBrowserEngine;
|
||||
}
|
||||
|
||||
// Gecko
|
||||
else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPGeckoBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPGeckoBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
|
||||
PLATFORM_FEATURES[CPBackspaceTriggersPageBack] = YES;
|
||||
|
||||
var index = USER_AGENT.indexOf("Firefox"),
|
||||
version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1));
|
||||
@@ -208,6 +246,9 @@ else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
|
||||
if (version < 3.0)
|
||||
PLATFORM_FEATURES[CPJavaScriptMouseWheelValues_8_15] = YES;
|
||||
|
||||
if (version >= 66)
|
||||
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
|
||||
|
||||
// Some day this might be fixed and should be version prefixed. No known fixed version yet.
|
||||
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;
|
||||
|
||||
@@ -286,7 +327,7 @@ function CPPlatformHasBug(aBug)
|
||||
|
||||
function CPBrowserIsEngine(anEngine)
|
||||
{
|
||||
return PLATFORM_ENGINE === anEngine;
|
||||
return PLATFORM_ENGINE & anEngine;
|
||||
}
|
||||
|
||||
function CPBrowserIsOperatingSystem(anOperatingSystem)
|
||||
@@ -322,6 +363,14 @@ else
|
||||
CPRedoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a feature with the given value.
|
||||
*/
|
||||
function CPSetPlatformFeature(aFeature, aBool)
|
||||
{
|
||||
PLATFORM_FEATURES[aFeature] = aBool;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the properly prefixed JS property for the given name. E.g. in a webkit browser,
|
||||
CPBrowserStyleProperty('transition') -> WebkitTransition
|
||||
@@ -369,6 +418,18 @@ function CPBrowserStyleProperty(aProperty)
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transform']] || nil;
|
||||
break;
|
||||
|
||||
case 'animationend':
|
||||
var candidates = {
|
||||
'WebkitAnimation' : 'webkitAnimationEnd',
|
||||
'MozAnimation' : 'animationend',
|
||||
'OAnimation' : 'oAnimationEnd',
|
||||
'msAnimation' : 'MSAnimationEnd',
|
||||
'animation' : 'animationend'
|
||||
};
|
||||
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['animation']] || nil;
|
||||
break;
|
||||
|
||||
default:
|
||||
var prefixes = ["Webkit", "Moz", "O", "ms"],
|
||||
strippedProperty = aProperty.split('-').join(' '),
|
||||
|
||||
+41
-22
@@ -23,12 +23,13 @@
|
||||
@import <Foundation/CPFormatter.j>
|
||||
@import <Foundation/CPTimer.j>
|
||||
|
||||
@import "CPFont.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPView.j"
|
||||
@import "CPText.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "CPTrackingArea.j"
|
||||
|
||||
@class CPFont
|
||||
|
||||
@global CPApp
|
||||
|
||||
@protocol CPControlTextEditingDelegate <CPObject>
|
||||
@@ -48,6 +49,9 @@ CPRegularControlSize = 0;
|
||||
CPSmallControlSize = 1;
|
||||
CPMiniControlSize = 2;
|
||||
|
||||
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
|
||||
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
|
||||
|
||||
@typedef CPLineBreakMode
|
||||
CPLineBreakByWordWrapping = 0;
|
||||
CPLineBreakByCharWrapping = 1;
|
||||
@@ -131,14 +135,13 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
@"vertical-alignment": CPTopVerticalTextAlignment,
|
||||
@"line-break-mode": CPLineBreakByClipping,
|
||||
@"text-color": [CPColor blackColor],
|
||||
@"font": [CPFont systemFontOfSize:CPFontCurrentSystemSize],
|
||||
@"font": [CPNull null],
|
||||
@"text-shadow-color": [CPNull null],
|
||||
@"text-shadow-offset": CGSizeMakeZero(),
|
||||
@"image-position": CPImageLeft,
|
||||
@"image-scaling": CPScaleToFit,
|
||||
@"min-size": CGSizeMakeZero(),
|
||||
@"max-size": CGSizeMake(-1.0, -1.0),
|
||||
@"nib2cib-adjustment-frame": CGRectMakeZero()
|
||||
@"max-size": CGSizeMake(-1.0, -1.0)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,7 +172,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Reverse set the binding iff the CPContinuouslyUpdatesValueBindingOption is set.
|
||||
Reverse set the binding if the CPContinuouslyUpdatesValueBindingOption is set.
|
||||
*/
|
||||
- (void)_continuouslyReverseSetBinding
|
||||
{
|
||||
@@ -196,6 +199,9 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
{
|
||||
_sendActionOn = CPLeftMouseUpMask;
|
||||
_trackingMouseDownFlags = 0;
|
||||
|
||||
[self setControlSize:CPThemeStateControlSizeRegular];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -620,15 +626,15 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
*/
|
||||
- (CPString)stringValue
|
||||
{
|
||||
if (_formatter && _value !== undefined)
|
||||
if (_formatter && _value != nil)
|
||||
{
|
||||
var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value];
|
||||
|
||||
if (formattedValue !== nil && formattedValue !== undefined)
|
||||
if (formattedValue != nil)
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
return (_value === undefined || _value === nil) ? @"" : String(_value);
|
||||
return _value == nil ? @"" : String(_value);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -637,7 +643,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
- (void)setStringValue:(CPString)aString
|
||||
{
|
||||
// Cocoa raises an invalid parameter assertion and returns if you pass nil.
|
||||
if (aString === nil || aString === undefined)
|
||||
if (aString == nil)
|
||||
{
|
||||
CPLog.warn("nil or undefined sent to CPControl -setStringValue");
|
||||
return;
|
||||
@@ -803,7 +809,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
CPBottomVerticalTextAlignment
|
||||
</pre>
|
||||
*/
|
||||
- (void)setVerticalAlignment:(CPTextVerticalAlignment)alignment
|
||||
- (void)setVerticalAlignment:(CPVerticalTextAlignment)alignment
|
||||
{
|
||||
[self setValue:alignment forThemeAttribute:@"vertical-alignment"];
|
||||
}
|
||||
@@ -848,7 +854,12 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
*/
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
[self setValue:aColor forThemeAttribute:@"text-color"];
|
||||
[self setValue:aColor forThemeAttribute:@"text-color" inState:[self themeState]];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor inThemeStates:(CPArray)themeStates
|
||||
{
|
||||
[self setValue:aColor forThemeAttribute:@"text-color" inStates:themeStates];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -906,7 +917,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
*/
|
||||
- (CPFont)font
|
||||
{
|
||||
return [self valueForThemeAttribute:@"font"];
|
||||
return [self currentValueForThemeAttribute:@"font"] || [CPFont systemFontForControlSize:_controlSize];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1057,14 +1068,21 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
@end
|
||||
|
||||
@implementation CPControl (CPTrackingArea)
|
||||
{
|
||||
CPTrackingArea _controlTrackingArea;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[self removeAllTrackingAreas];
|
||||
[self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
|
||||
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil]];
|
||||
if (_controlTrackingArea)
|
||||
[self removeTrackingArea:_controlTrackingArea];
|
||||
|
||||
_controlTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
|
||||
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:_controlTrackingArea];
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1109,6 +1127,7 @@ var CPControlActionKey = @"CPControlActionKey",
|
||||
[self setControlSize:[aCoder decodeIntForKey:CPControlControlSizeKey]];
|
||||
|
||||
[self setBaseWritingDirection:[aCoder decodeIntForKey:CPControlBaseWrittingDirectionKey]];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -1128,18 +1147,18 @@ var CPControlActionKey = @"CPControlActionKey",
|
||||
|
||||
var objectValue = [self objectValue];
|
||||
|
||||
if (objectValue !== nil)
|
||||
if (objectValue != nil)
|
||||
[aCoder encodeObject:objectValue forKey:CPControlValueKey];
|
||||
|
||||
if (_target !== nil)
|
||||
if (_target != nil)
|
||||
[aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
|
||||
|
||||
if (_action !== nil)
|
||||
if (_action != nil)
|
||||
[aCoder encodeObject:_action forKey:CPControlActionKey];
|
||||
|
||||
[aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
|
||||
|
||||
if (_formatter !== nil)
|
||||
if (_formatter != nil)
|
||||
[aCoder encodeObject:_formatter forKey:CPControlFormatterKey];
|
||||
|
||||
[aCoder encodeInt:_controlSize forKey:CPControlControlSizeKey];
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
CPString _expires;
|
||||
}
|
||||
|
||||
@global document
|
||||
|
||||
/*!
|
||||
Initializes a cookie with a given name \c aName.
|
||||
@param the name for the cookie
|
||||
|
||||
Executable → Regular
+112
-44
@@ -23,6 +23,7 @@ Cursor support by browser:
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import "CPImage.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
@@ -31,6 +32,12 @@ var currentCursor = nil,
|
||||
cursors = {},
|
||||
ieCursorMap = {};
|
||||
|
||||
@typedef CPCursorPlatform
|
||||
CPCursorPlatformNone = 0;
|
||||
CPCursorPlatformMac = 1;
|
||||
CPCursorPlatformWindows = 2;
|
||||
CPCursorPlatformBoth = 3;
|
||||
|
||||
@implementation CPCursor : CPObject
|
||||
{
|
||||
CPString _cssString @accessors(readonly);
|
||||
@@ -157,7 +164,7 @@ var currentCursor = nil,
|
||||
}
|
||||
|
||||
// Internal method that is used to return the system cursors. Caches the system cursors for performance.
|
||||
+ (CPCursor)_systemCursorWithName:(CPString)cursorName cssString:(CPString)aString hasImage:(BOOL)doesHaveImage
|
||||
+ (CPCursor)_nativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)aString
|
||||
{
|
||||
var cursor = cursors[cursorName];
|
||||
|
||||
@@ -165,155 +172,216 @@ var currentCursor = nil,
|
||||
{
|
||||
var cssString;
|
||||
|
||||
if (doesHaveImage)
|
||||
{
|
||||
var themeResourcePath = [[[CPApp themeBlend] bundle] resourcePath],
|
||||
extension = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) ? @"cur" : @"png";
|
||||
|
||||
cssString = [CPString stringWithFormat:@"url(%@cursors/%@.%@), %@", themeResourcePath, cursorName, extension, aString];
|
||||
}
|
||||
// IE <= 8 does not support some cursors, map them to supported cursors
|
||||
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
|
||||
|
||||
if (ieLessThan9)
|
||||
cssString = ieCursorMap[aString] || aString;
|
||||
else
|
||||
{
|
||||
// IE <= 8 does not support some cursors, map them to supported cursors
|
||||
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
|
||||
cssString = aString;
|
||||
|
||||
if (ieLessThan9)
|
||||
cssString = ieCursorMap[aString] || aString;
|
||||
else
|
||||
cssString = aString;
|
||||
}
|
||||
|
||||
cursor = [[CPCursor alloc] initWithCSSString:cssString];
|
||||
cursors[cursorName] = cursor;
|
||||
cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
+ (CPCursor)_imageCursorWithName:(CPString)cursorName cssString:(CPString)aString
|
||||
{
|
||||
var cursor = cursors[cursorName];
|
||||
|
||||
if (typeof cursor === "undefined")
|
||||
{
|
||||
var themeResourcePath = [[[CPApp themeBlend] bundle] resourcePath],
|
||||
extension = CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) ? @"cur" : @"png",
|
||||
cssString = [CPString stringWithFormat:@"url(%@cursors/%@.%@), %@", themeResourcePath, cursorName, extension, aString];
|
||||
|
||||
cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
+ (CPCursor)_tryUsingNativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)cssName onPlatform:(CPCursorPlatform)shouldUseNativeCursorOn fallingBackWithImageAndCSSPointer:(CPString)aString
|
||||
{
|
||||
var useNativeSystemCursor = (((shouldUseNativeCursorOn == CPCursorPlatformBoth) ||
|
||||
((shouldUseNativeCursorOn == CPCursorPlatformMac) && CPBrowserIsOperatingSystem(CPMacOperatingSystem)) ||
|
||||
((shouldUseNativeCursorOn == CPCursorPlatformWindows) && CPBrowserIsOperatingSystem(CPWindowsOperatingSystem)))
|
||||
&& [CPCursor _nativeCursorExists:cssName]);
|
||||
|
||||
if (useNativeSystemCursor)
|
||||
return [CPCursor _nativeSystemCursorWithName:cursorName cssString:cssName];
|
||||
else
|
||||
return [CPCursor _imageCursorWithName:cursorName cssString:aString];
|
||||
}
|
||||
|
||||
+ (BOOL)_nativeCursorExists:(CPString)cursorCSSName
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
// FIXME: Trick until FF/Win & Chrome/Win correctly implement context-menu cursor
|
||||
// They will answer that they implement it but they actually don't
|
||||
|
||||
if ([cursorCSSName isEqualToString:@"context-menu"] && CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPBrowserIsEngine(CPEdgeBrowserEngine))
|
||||
return NO;
|
||||
|
||||
// Normal usage : try to set the cursor and check if resulting cursor is the one we tried to set.
|
||||
// If yes, then the browser implements the cursor. If no (and usually we get "default"), then it doesn't.
|
||||
|
||||
var platformWindows = [[CPPlatformWindow visiblePlatformWindows] allObjects],
|
||||
count = [platformWindows count];
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
var currentPlatformCursor = platformWindows[0]._DOMBodyElement.style.cursor;
|
||||
platformWindows[0]._DOMBodyElement.style.cursor = cursorCSSName;
|
||||
var doesExist = (platformWindows[0]._DOMBodyElement.style.cursor == cursorCSSName);
|
||||
platformWindows[0]._DOMBodyElement.style.cursor = currentPlatformCursor;
|
||||
|
||||
return doesExist;
|
||||
}
|
||||
#endif
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (CPCursor)arrowCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default"];
|
||||
}
|
||||
|
||||
+ (CPCursor)crosshairCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair"];
|
||||
}
|
||||
|
||||
+ (CPCursor)IBeamCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text"];
|
||||
}
|
||||
|
||||
+ (CPCursor)pointingHandCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthwestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthwestSoutheastCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNortheastCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNortheastSouthwestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeSouthwestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeSoutheastCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeDownCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeUpCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeLeftCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeRightCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeLeftRightCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeEastWestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeUpDownCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthSouthCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)operationNotAllowedCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed" hasImage:NO];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed"];
|
||||
}
|
||||
|
||||
+ (CPCursor)dragCopyCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy" hasImage:YES];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy"];
|
||||
}
|
||||
|
||||
+ (CPCursor)dragLinkCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias" hasImage:YES];
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias"];
|
||||
}
|
||||
|
||||
+ (CPCursor)contextualMenuCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"context-menu" hasImage:YES];
|
||||
return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
|
||||
cssString:@"context-menu"
|
||||
onPlatform:CPCursorPlatformBoth
|
||||
fallingBackWithImageAndCSSPointer:@"default"];
|
||||
}
|
||||
|
||||
+ (CPCursor)openHandCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"move" hasImage:YES];
|
||||
return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
|
||||
cssString:@"grab"
|
||||
onPlatform:CPCursorPlatformMac
|
||||
fallingBackWithImageAndCSSPointer:@"default"];
|
||||
}
|
||||
|
||||
+ (CPCursor)closedHandCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"-moz-grabbing" hasImage:YES];
|
||||
return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
|
||||
cssString:@"grabbing"
|
||||
onPlatform:CPCursorPlatformMac
|
||||
fallingBackWithImageAndCSSPointer:@"default"];
|
||||
}
|
||||
|
||||
+ (CPCursor)disappearingItemCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"auto" hasImage:YES];
|
||||
return [CPCursor _imageCursorWithName:CPStringFromSelector(_cmd) cssString:@"default"];
|
||||
}
|
||||
|
||||
+ (CPCursor)IBeamCursorForVerticalLayout
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"vertical-text"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* CPDatePicker.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
@@ -59,26 +59,26 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
@implementation CPDatePicker : CPControl
|
||||
{
|
||||
BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:);
|
||||
BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:);
|
||||
BOOL _drawsBackground @accessors(property=drawsBackground);
|
||||
CPDate _dateValue @accessors(property=dateValue);
|
||||
CPDate _minDate @accessors(property=minDate);
|
||||
CPDate _maxDate @accessors(property=maxDate);
|
||||
CPFont _textFont @accessors(property=textFont);
|
||||
CPLocale _locale @accessors(property=locale);
|
||||
//CPCalendar _calendar @accessors(property=calendar);
|
||||
CPTimeZone _timeZone @accessors(property=timeZone);
|
||||
id _delegate @accessors(property=delegate);
|
||||
CPInteger _datePickerElements @accessors(property=datePickerElements);
|
||||
CPInteger _datePickerMode @accessors(property=datePickerMode);
|
||||
CPInteger _datePickerStyle @accessors(property=datePickerStyle);
|
||||
CPInteger _timeInterval @accessors(property=timeInterval);
|
||||
BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:);
|
||||
BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:);
|
||||
BOOL _drawsBackground @accessors(property=drawsBackground);
|
||||
CPDate _dateValue @accessors(property=dateValue);
|
||||
CPDate _minDate @accessors(property=minDate);
|
||||
CPDate _maxDate @accessors(property=maxDate);
|
||||
CPFont _textFont @accessors(property=textFont);
|
||||
CPLocale _locale @accessors(property=locale);
|
||||
//CPCalendar _calendar @accessors(property=calendar);
|
||||
CPTimeZone _timeZone @accessors(property=timeZone);
|
||||
id _delegate @accessors(property=delegate);
|
||||
CPInteger _datePickerElements @accessors(property=datePickerElements);
|
||||
CPInteger _datePickerMode @accessors(property=datePickerMode);
|
||||
CPInteger _datePickerStyle @accessors(property=datePickerStyle);
|
||||
CPInteger _timeInterval @accessors(property=timeInterval);
|
||||
|
||||
BOOL _invokedByUserEvent;
|
||||
_CPDatePickerTextField _datePickerTextfield;
|
||||
_CPDatePickerCalendar _datePickerCalendar;
|
||||
unsigned _implementedCDatePickerDelegateMethods;
|
||||
BOOL _isTextual;
|
||||
id _datePickerComponent;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,32 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
@"hour-hand-size": CGSizeMakeZero(),
|
||||
@"middle-hand-size": CGSizeMakeZero(),
|
||||
@"minute-hand-size": CGSizeMakeZero(),
|
||||
@"previous-button-size": CGSizeMakeZero(),
|
||||
@"current-button-size": CGSizeMakeZero(),
|
||||
@"next-button-size": CGSizeMakeZero(),
|
||||
@"title-inset": [CPNull null],
|
||||
@"day-label-inset": [CPNull null],
|
||||
@"tile-content-inset": CGInsetMakeZero(),
|
||||
@"tile-margin": [CPNull null],
|
||||
@"tile-inset": CGInsetMakeZero(),
|
||||
@"separator-color": [CPNull null],
|
||||
@"separator-margin-width": 0,
|
||||
@"separator-height": 0,
|
||||
@"bezel-color-calendar-left": [CPNull null],
|
||||
@"bezel-color-calendar-middle": [CPNull null],
|
||||
@"bezel-color-calendar-right": [CPNull null],
|
||||
@"tile-vertical-alignment": CPCenterVerticalTextAlignment,
|
||||
@"tile-text-alignment": CPCenterTextAlignment,
|
||||
@"hour-ampm-margin": 2,
|
||||
@"time-separator-content-inset": CGInsetMakeZero(),
|
||||
@"clock-second-hand-over": NO,
|
||||
@"clock-draws-hours": NO,
|
||||
@"clock-hours-font": [CPNull null],
|
||||
@"clock-hours-text-color": [CPColor clearColor],
|
||||
@"clock-hours-radius": 0,
|
||||
@"calendar-clock-margin": 10,
|
||||
@"clock-only-nib2cib-adjustment-frame": CPRectMakeZero(),
|
||||
@"uses-focus-ring": NO
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,19 +232,44 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
if (!_locale)
|
||||
_locale = [CPLocale currentLocale];
|
||||
|
||||
_datePickerTextfield = [[_CPDatePickerTextField alloc] initWithFrame:[self bounds] withDatePicker:self];
|
||||
[_datePickerTextfield setDateValue:_dateValue];
|
||||
_datePickerComponent = nil;
|
||||
|
||||
_datePickerCalendar = [[_CPDatePickerCalendar alloc] initWithFrame:[self bounds] withDatePicker:self];
|
||||
[_datePickerCalendar setDateValue:_dateValue];
|
||||
|
||||
// We might have been unarchived in a disabled state.
|
||||
[_datePickerTextfield setEnabled:[self isEnabled]];
|
||||
[self _createComponents];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)_createComponents
|
||||
{
|
||||
_isTextual = (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle) || (_datePickerStyle == CPTextFieldDatePickerStyle);
|
||||
|
||||
if (_datePickerComponent)
|
||||
{
|
||||
[_datePickerComponent removeFromSuperview];
|
||||
|
||||
_datePickerComponent = nil;
|
||||
}
|
||||
|
||||
_datePickerComponent = [[(_isTextual ? _CPDatePickerTextField : _CPDatePickerCalendar) alloc] initWithFrame:[self bounds] withDatePicker:self];
|
||||
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
[_datePickerComponent setControlSize:[self controlSize]];
|
||||
[_datePickerComponent setDatePickerElements:_datePickerElements];
|
||||
|
||||
// FIXME: Don't know why but next line will cause theme compilation to fail...
|
||||
// Workaround: added "if PLATFORM(DOM)"
|
||||
#if PLATFORM(DOM)
|
||||
[_datePickerComponent setEnabled:[self isEnabled]];
|
||||
#endif
|
||||
|
||||
if (_isTextual)
|
||||
// We need to transmit text color to the text field version (Cocoa doesn't permit adapting the calendar view text color)
|
||||
[_datePickerComponent setTextColor:[self textColor]];
|
||||
|
||||
[self addSubview:_datePickerComponent];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Control Size
|
||||
@@ -227,7 +278,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
{
|
||||
[super setControlSize:aControlSize];
|
||||
|
||||
if ([self datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [self datePickerStyle] == CPTextFieldDatePickerStyle)
|
||||
[_datePickerComponent setControlSize:aControlSize];
|
||||
|
||||
if (_isTextual)
|
||||
[self _sizeToControlSize];
|
||||
}
|
||||
|
||||
@@ -256,31 +309,8 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
{
|
||||
if (![_datePickerTextfield superview])
|
||||
[self addSubview:_datePickerTextfield];
|
||||
|
||||
if ([_datePickerCalendar superview])
|
||||
[_datePickerCalendar removeFromSuperview];
|
||||
|
||||
[_datePickerTextfield setControlSize:[self controlSize]];
|
||||
[_datePickerTextfield setNeedsLayout];
|
||||
[_datePickerTextfield setNeedsDisplay:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (![_datePickerCalendar superview])
|
||||
[self addSubview:_datePickerCalendar];
|
||||
|
||||
if ([_datePickerTextfield superview])
|
||||
[_datePickerTextfield removeFromSuperview];
|
||||
|
||||
[_datePickerCalendar setNeedsLayout];
|
||||
[_datePickerCalendar setNeedsDisplay:YES];
|
||||
}
|
||||
[_datePickerComponent setNeedsLayout];
|
||||
[_datePickerComponent setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -341,10 +371,7 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
|
||||
if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval)
|
||||
{
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
[_datePickerTextfield setDateValue:_dateValue];
|
||||
else
|
||||
[_datePickerCalendar setDateValue:_dateValue];
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -352,8 +379,8 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
if (_implementedCDatePickerDelegateMethods & CPDatePicker_validateProposedDateValue_timeInterval)
|
||||
{
|
||||
// constrain timeInterval also
|
||||
var aStartDateRef = function(x){if (typeof x == 'undefined') return aDateValue; aDateValue = x;}
|
||||
var aTimeIntervalRef = function(x){if (typeof x == 'undefined') return aTimeInterval; aTimeInterval = x;}
|
||||
var aStartDateRef = function(x){if (typeof x == 'undefined') return aDateValue; aDateValue = x;};
|
||||
var aTimeIntervalRef = function(x){if (typeof x == 'undefined') return aTimeInterval; aTimeInterval = x;};
|
||||
|
||||
[_delegate datePicker:self validateProposedDateValue:aStartDateRef timeInterval:aTimeIntervalRef];
|
||||
}
|
||||
@@ -372,10 +399,7 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
if (_invokedByUserEvent)
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
[_datePickerTextfield setDateValue:_dateValue];
|
||||
else
|
||||
[_datePickerCalendar setDateValue:_dateValue];
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
}
|
||||
|
||||
/*! Set the minDate of the datePicker
|
||||
@@ -383,6 +407,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setMinDate:(CPDate)aMinDate
|
||||
{
|
||||
if (_minDate === aMinDate)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"minDate"];
|
||||
_minDate = aMinDate;
|
||||
[self didChangeValueForKey:@"minDate"];
|
||||
@@ -395,6 +422,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setMaxDate:(CPDate)aMaxDate
|
||||
{
|
||||
if (_maxDate === aMaxDate)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"maxDate"];
|
||||
_maxDate = aMaxDate;
|
||||
[self didChangeValueForKey:@"maxDate"];
|
||||
@@ -407,10 +437,32 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setDatePickerStyle:(CPInteger)aDatePickerStyle
|
||||
{
|
||||
if (_datePickerStyle === aDatePickerStyle)
|
||||
return;
|
||||
|
||||
_datePickerStyle = aDatePickerStyle;
|
||||
|
||||
// This is needed in order to specify different theme attributes values for textual / graphical date picker
|
||||
if (_datePickerStyle === CPClockAndCalendarDatePickerStyle)
|
||||
[self setThemeState:CPThemeStateAlternateState];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateAlternateState];
|
||||
|
||||
// This is needed in order to specify different theme attributes values for with / without stepper textual date picker
|
||||
if (_datePickerStyle === CPTextFieldAndStepperDatePickerStyle)
|
||||
[self setThemeState:CPThemeStateComposedControl];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateComposedControl];
|
||||
|
||||
[self setControlSize:[self controlSize]];
|
||||
|
||||
if (_datePickerComponent)
|
||||
{
|
||||
// We already have a component so we need to update it
|
||||
[_datePickerComponent resignFirstResponder];
|
||||
[self _createComponents];
|
||||
}
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@@ -420,8 +472,14 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
|
||||
{
|
||||
if (_datePickerElements === aDatePickerElements)
|
||||
return;
|
||||
|
||||
_datePickerElements = aDatePickerElements;
|
||||
|
||||
// Notify the component of the new value
|
||||
[_datePickerComponent setDatePickerElements:_datePickerElements];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@@ -431,6 +489,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setDatePickerMode:(CPInteger)aDatePickerMode
|
||||
{
|
||||
if (_datePickerMode === aDatePickerMode)
|
||||
return;
|
||||
|
||||
_datePickerMode = aDatePickerMode;
|
||||
|
||||
if (_datePickerMode == CPSingleDateMode)
|
||||
@@ -456,6 +517,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setLocale:(CPLocale)aLocale
|
||||
{
|
||||
if (_locale === aLocale)
|
||||
return;
|
||||
|
||||
_locale = aLocale;
|
||||
|
||||
if (_formatter)
|
||||
@@ -466,7 +530,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
}
|
||||
|
||||
// This will update the textFields (usefull when changing with a date with pm and am)
|
||||
[_datePickerTextfield setDateValue:_dateValue];
|
||||
if (_isTextual)
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@@ -477,6 +543,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setBezeled:(BOOL)shouldBeBezeled
|
||||
{
|
||||
if (_isBezeled === shouldBeBezeled)
|
||||
return;
|
||||
|
||||
_isBezeled = shouldBeBezeled;
|
||||
|
||||
if (shouldBeBezeled)
|
||||
@@ -491,6 +560,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (_isBordered === shouldBeBordered)
|
||||
return;
|
||||
|
||||
_isBordered = shouldBeBordered;
|
||||
|
||||
if (shouldBeBordered)
|
||||
@@ -506,6 +578,22 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
- (void)setTextFont:(CPFont)aFont
|
||||
{
|
||||
[self setFont:aFont];
|
||||
|
||||
if (_isTextual)
|
||||
[_datePickerComponent setTextFont:aFont];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color of the control.
|
||||
@param aColor
|
||||
*/
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
[super setTextColor:aColor];
|
||||
|
||||
if (_isTextual)
|
||||
[_datePickerComponent setTextColor:aColor];
|
||||
// REM: in Cocoa, setTextColor has no effect on calendar view
|
||||
}
|
||||
|
||||
/*! Sets the enabled status of the control. Controls that are not enabled can not be used by the user and obtain the CPThemeStateDisabled theme state.
|
||||
@@ -515,8 +603,10 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
{
|
||||
[super setEnabled:aBoolean];
|
||||
|
||||
[_datePickerTextfield setEnabled:aBoolean];
|
||||
[_datePickerCalendar setEnabled:aBoolean];
|
||||
[_datePickerComponent setEnabled:aBoolean];
|
||||
|
||||
if (!aBoolean)
|
||||
[self resignFirstResponder];
|
||||
}
|
||||
|
||||
/*! Set the background color of the datePicker
|
||||
@@ -533,6 +623,9 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setDrawsBackground:(BOOL)aBoolean
|
||||
{
|
||||
if (_drawsBackground === aBoolean)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"drawsBackground"];
|
||||
_drawsBackground = aBoolean;
|
||||
[self didChangeValueForKey:@"drawsBackground"];
|
||||
@@ -545,16 +638,16 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)setTimeZone:(CPTimeZone)aTimeZone
|
||||
{
|
||||
if (_timeZone === aTimeZone)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"timeZone"];
|
||||
_timeZone = aTimeZone;
|
||||
[self didChangeValueForKey:@"timeZone"];
|
||||
|
||||
[self setNeedsLayout];
|
||||
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
[_datePickerTextfield setDateValue:_dateValue];
|
||||
else
|
||||
[_datePickerCalendar setDateValue:_dateValue];
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
}
|
||||
|
||||
|
||||
@@ -565,12 +658,12 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
if (_isTextual)
|
||||
{
|
||||
if (![super becomeFirstResponder])
|
||||
return NO;
|
||||
|
||||
[_datePickerTextfield _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]];
|
||||
[_datePickerComponent _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -589,8 +682,8 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
[_datePickerTextfield resignFirstResponder];
|
||||
if (_isTextual)
|
||||
[_datePickerComponent resignFirstResponder];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -638,8 +731,8 @@ CPEraDatePickerElementFlag = 0x0100;
|
||||
*/
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
|
||||
[_datePickerTextfield keyDown:anEvent];
|
||||
if (_isTextual)
|
||||
[_datePickerComponent keyDown:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -665,20 +758,22 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
|
||||
|
||||
if (self)
|
||||
{
|
||||
_textFont = [aCoder decodeObjectForKey:CPTextFontKey];
|
||||
_minDate = [aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast];
|
||||
_maxDate = [aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture];
|
||||
_timeInterval = [aCoder decodeDoubleForKey:CPIntervalKey];
|
||||
_datePickerMode = [aCoder decodeIntForKey:CPDatePickerModeKey];
|
||||
_datePickerElements = [aCoder decodeIntForKey:CPDatePickerElementsKey];
|
||||
[self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]];
|
||||
_locale = [aCoder decodeObjectForKey:CPLocaleKey];
|
||||
_dateValue = [aCoder decodeObjectForKey:CPDateValueKey];
|
||||
_backgroundColor = [aCoder decodeObjectForKey:CPBackgroundColorKey];
|
||||
_drawsBackground = [aCoder decodeBoolForKey:CPDrawsBackgroundKey];
|
||||
_isBordered = [aCoder decodeBoolForKey:CPBorderedKey];
|
||||
[self setBordered:[aCoder decodeBoolForKey:CPBorderedKey]];
|
||||
[self setDrawsBackground:[aCoder decodeBoolForKey:CPDrawsBackgroundKey]];
|
||||
|
||||
[self setDatePickerElements:[aCoder decodeIntForKey:CPDatePickerElementsKey]];
|
||||
[self setDatePickerMode:[aCoder decodeIntForKey:CPDatePickerModeKey]];
|
||||
[self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]];
|
||||
[self setMinDate:[aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast]];
|
||||
[self setMaxDate:[aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture]];
|
||||
[self setLocale:[aCoder decodeObjectForKey:CPLocaleKey]];
|
||||
|
||||
[self _init];
|
||||
|
||||
[self setTextFont:[aCoder decodeObjectForKey:CPTextFontKey]];
|
||||
[self setTimeInterval:[aCoder decodeDoubleForKey:CPIntervalKey]];
|
||||
[self setDateValue:[aCoder decodeObjectForKey:CPDateValueKey]];
|
||||
}
|
||||
|
||||
return self
|
||||
@@ -686,7 +781,10 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// FIXME Do we need to encode _datePickerTextfield and _datePickerCalendar? As subviews they'll be encoded, but when we decode we recreate them anyhow.
|
||||
// Before encoding, we remove all subviews as we'll recreate them at loading
|
||||
while ([[self subviews] count] > 0)
|
||||
[[[self subviews] lastObject] removeFromSuperview];
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeDouble:_timeInterval forKey:CPIntervalKey];
|
||||
@@ -694,8 +792,8 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
|
||||
[aCoder encodeInt:_datePickerStyle forKey:CPDatePickerStyleKey];
|
||||
[aCoder encodeInt:_datePickerElements forKey:CPDatePickerElementsKey];
|
||||
[aCoder encodeObject:_minDate forKey:CPMinDateKey];
|
||||
[aCoder encodeObject:_maxDate forKey:CPMaxDateKey]
|
||||
[aCoder encodeObject:_dateValue forKey:CPDateValueKey];;
|
||||
[aCoder encodeObject:_maxDate forKey:CPMaxDateKey];
|
||||
[aCoder encodeObject:_dateValue forKey:CPDateValueKey];
|
||||
[aCoder encodeObject:_textFont forKey:CPTextFontKey];
|
||||
[aCoder encodeObject:_locale forKey:CPLocaleKey];
|
||||
[aCoder encodeObject:_backgroundColor forKey:CPBackgroundColorKey];
|
||||
@@ -706,6 +804,7 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
|
||||
|
||||
@end
|
||||
|
||||
// FIXME: add support for CPEditorRegistrationProtocol as implemented for CPTextField
|
||||
@implementation _CPDatePickerValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/* _CPDatePickerBox.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
@import "CPView.j"
|
||||
|
||||
@class CPDatePicker
|
||||
|
||||
@implementation _CPDatePickerBox : CPView
|
||||
{
|
||||
CPDatePicker _datePicker @accessors(property=datePicker);
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
[super drawRect:aRect];
|
||||
|
||||
if ([_datePicker isCSSBased])
|
||||
return;
|
||||
|
||||
if ([_datePicker isBordered])
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2;
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]);
|
||||
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
|
||||
|
||||
CGContextMoveToPoint(context, borderWidth, borderWidth);
|
||||
CGContextAddLineToPoint(context, aRect.size.width - borderWidth, borderWidth);
|
||||
CGContextAddLineToPoint(context, aRect.size.width - borderWidth, aRect.size.height - borderWidth);
|
||||
CGContextAddLineToPoint(context, borderWidth, aRect.size.height - borderWidth);
|
||||
CGContextAddLineToPoint(context, borderWidth,borderWidth);
|
||||
|
||||
CGContextStrokePath(context);
|
||||
CGContextClosePath(context);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "bezel-view")
|
||||
return [self bounds];
|
||||
|
||||
return [super rectForEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "bezel-view")
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
return [super createEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if ([_datePicker isCSSBased])
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[bezelView setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color"]];
|
||||
}
|
||||
|
||||
if ([_datePicker drawsBackground])
|
||||
[self setBackgroundColor:[_datePicker backgroundColor]];
|
||||
else
|
||||
[self setBackgroundColor:[CPColor clearColor]];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,13 +20,18 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPKeyedArchiver.j>
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
@import "CPView.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPImage.j"
|
||||
@import "CALayer.j"
|
||||
|
||||
@class _CPCibCustomResource
|
||||
@class CPDatePicker
|
||||
@class HandImageLayer
|
||||
@class HoursLayer
|
||||
@class HandLayer
|
||||
|
||||
@global CPHourMinuteSecondDatePickerElementFlag
|
||||
@global CPTextFieldAndStepperDatePickerStyle
|
||||
@@ -34,17 +39,35 @@
|
||||
|
||||
var RADIANS = Math.PI / 180;
|
||||
|
||||
@typedef _CPDatePickerClockHand
|
||||
_CPDatePickerClockHours = 1;
|
||||
_CPDatePickerClockMinutes = 2;
|
||||
_CPDatePickerClockSeconds = 3;
|
||||
|
||||
@implementation _CPDatePickerClock : CPView
|
||||
@implementation _CPDatePickerClock : CPControl
|
||||
{
|
||||
BOOL _isEnabled;
|
||||
CALayer _rootLayer;
|
||||
CALayer _hourHandLayer;
|
||||
CALayer _minuteHandLayer;
|
||||
CALayer _secondHandLayer;
|
||||
CALayer _middleHandLayer;
|
||||
CPDatePicker _datePicker;
|
||||
CPTextField _PMAMTextField;
|
||||
BOOL _isEnabled;
|
||||
HoursLayer _rootLayer;
|
||||
HandLayer _hourHandLayer;
|
||||
HandLayer _minuteHandLayer;
|
||||
HandLayer _secondHandLayer;
|
||||
CALayer _middleHandLayer;
|
||||
CPDatePicker _datePicker;
|
||||
CPTextField _PMAMTextField;
|
||||
|
||||
CALayer _currentHandLayer;
|
||||
_CPDatePickerClockHand _currentHand;
|
||||
CPInteger _currentRepresentedValue;
|
||||
float _currentValueShift;
|
||||
CPInteger _numberOfUnits;
|
||||
BOOL _trackingHand;
|
||||
|
||||
CPInteger _representedHours;
|
||||
CPInteger _representedMinutes;
|
||||
CPInteger _representedSeconds;
|
||||
BOOL _representedHourIsPM;
|
||||
|
||||
CPInteger _datePickerElements @accessors(getter=datePickerElements);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +78,9 @@ var RADIANS = Math.PI / 180;
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_datePicker = aDatePicker;
|
||||
_datePicker = aDatePicker;
|
||||
_datePickerElements = [_datePicker datePickerElements];
|
||||
_trackingHand = NO;
|
||||
|
||||
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
@@ -73,7 +98,7 @@ var RADIANS = Math.PI / 180;
|
||||
|
||||
var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"],
|
||||
minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"],
|
||||
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
|
||||
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
|
||||
secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"];
|
||||
|
||||
// We use layer to make the rotation possible
|
||||
@@ -97,106 +122,132 @@ var RADIANS = Math.PI / 180;
|
||||
[_middleHandLayer setAnchorPoint:CGPointMakeZero()];
|
||||
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
|
||||
|
||||
_rootLayer = [CALayer layer];
|
||||
_rootLayer = [[HoursLayer alloc] init];
|
||||
[self setWantsLayer:YES];
|
||||
[self setLayer:_rootLayer];
|
||||
|
||||
[_hourHandLayer setNeedsDisplay];
|
||||
[_middleHandLayer setNeedsDisplay];
|
||||
[_secondHandLayer setNeedsDisplay];
|
||||
[_minuteHandLayer setNeedsDisplay];
|
||||
[self _initHands];
|
||||
|
||||
[_rootLayer addSublayer:_hourHandLayer];
|
||||
[_rootLayer addSublayer:_minuteHandLayer];
|
||||
[_rootLayer addSublayer:_secondHandLayer];
|
||||
[_rootLayer addSublayer:_middleHandLayer];
|
||||
|
||||
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
|
||||
{
|
||||
[_rootLayer addSublayer:_middleHandLayer];
|
||||
[_rootLayer addSublayer:_secondHandLayer];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_rootLayer addSublayer:_secondHandLayer];
|
||||
[_rootLayer addSublayer:_middleHandLayer];
|
||||
}
|
||||
|
||||
[_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]];
|
||||
[_rootLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Layout methods
|
||||
|
||||
- (void)layoutSubviews
|
||||
- (void)_initHands
|
||||
{
|
||||
if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle)
|
||||
return;
|
||||
var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"],
|
||||
hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"],
|
||||
minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"],
|
||||
secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"];
|
||||
|
||||
[super layoutSubviews];
|
||||
// If hand images are true CPImage, we have to duplicate them to avoid
|
||||
// the multiple delegates bug when multiple clocks are displayed
|
||||
|
||||
var bounds = [self bounds],
|
||||
dateValue = [[_datePicker dateValue] copy];
|
||||
if ([middleHandImage isKindOfClass:[CPImage class]])
|
||||
middleHandImage = [middleHandImage duplicate];
|
||||
|
||||
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
|
||||
if ([hourHandImage isKindOfClass:[CPImage class]])
|
||||
hourHandImage = [hourHandImage duplicate];
|
||||
|
||||
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]];
|
||||
[_middleHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"middle-hand-image"]];
|
||||
[_hourHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"hour-hand-image"]];
|
||||
[_minuteHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"minute-hand-image"]];
|
||||
[_secondHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"second-hand-image"]];
|
||||
if ([minuteHandImage isKindOfClass:[CPImage class]])
|
||||
minuteHandImage = [minuteHandImage duplicate];
|
||||
|
||||
if ([_datePicker _isAmericanFormat])
|
||||
{
|
||||
if (dateValue.getHours() > 11)
|
||||
[_PMAMTextField setStringValue:@"PM"]
|
||||
else
|
||||
[_PMAMTextField setStringValue:@"AM"];
|
||||
if ([secondHandImage isKindOfClass:[CPImage class]])
|
||||
secondHandImage = [secondHandImage duplicate];
|
||||
|
||||
[_PMAMTextField sizeToFit];
|
||||
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
|
||||
[_PMAMTextField setHidden:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_PMAMTextField setHidden:YES];
|
||||
}
|
||||
[_middleHandLayer setImage:middleHandImage];
|
||||
[_hourHandLayer setImage:hourHandImage];
|
||||
[_minuteHandLayer setImage:minuteHandImage];
|
||||
[_secondHandLayer setImage:secondHandImage];
|
||||
|
||||
[_hourHandLayer setRotationRadians:[self _hourPositionRadianForDate:dateValue]];
|
||||
[_minuteHandLayer setRotationRadians:[self _minutePositionRadianForDate:dateValue]];
|
||||
[_secondHandLayer setRotationRadians:[self _secondPositionRadianForDate:dateValue]];
|
||||
[_hourHandLayer setNeedsDisplay];
|
||||
[_middleHandLayer setNeedsDisplay];
|
||||
[_secondHandLayer setNeedsDisplay];
|
||||
[_minuteHandLayer setNeedsDisplay];
|
||||
|
||||
[_PMAMTextField setEnabled:_isEnabled];
|
||||
[_hourHandLayer setEnabled:_isEnabled];
|
||||
[_middleHandLayer setEnabled:_isEnabled];
|
||||
[_secondHandLayer setEnabled:_isEnabled];
|
||||
[_minuteHandLayer setEnabled:_isEnabled];
|
||||
|
||||
// Check if we have to display the hand second
|
||||
if (([_datePicker datePickerElements] & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)
|
||||
[_secondHandLayer setHidden:NO];
|
||||
else
|
||||
[_secondHandLayer setHidden:YES];
|
||||
[_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]];
|
||||
[_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]];
|
||||
[_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]];
|
||||
|
||||
[_rootLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
|
||||
{
|
||||
_datePickerElements = aDatePickerElements;
|
||||
|
||||
// Check if we have to display the hand second
|
||||
// FIXME: Don't know why but next line will cause theme compilation to fail...
|
||||
// Workaround: added "if PLATFORM(DOM)"
|
||||
#if PLATFORM(DOM)
|
||||
[_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark Layout methods
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
// While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement
|
||||
if (_trackingHand)
|
||||
return;
|
||||
|
||||
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]];
|
||||
|
||||
var dateValue = [[_datePicker dateValue] copy];
|
||||
|
||||
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
|
||||
|
||||
_representedHours = dateValue.getHours();
|
||||
_representedMinutes = dateValue.getMinutes();
|
||||
_representedSeconds = dateValue.getSeconds();
|
||||
_representedHourIsPM = (_representedHours > 11);
|
||||
|
||||
// Hours are expressed in 24 hours format, we need 12 hours format
|
||||
_representedHours -= (_representedHourIsPM ? 12 : 0);
|
||||
|
||||
[self _updateHands];
|
||||
|
||||
// FIXME: Workaround. Seems that CALayer doesn't redraw without an event
|
||||
[CALayer runLoopUpdateLayers];
|
||||
}
|
||||
|
||||
- (void)_updateHands
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
|
||||
[_PMAMTextField sizeToFit];
|
||||
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
|
||||
|
||||
[_hourHandLayer setRotationRadians:(360 * (_representedHours + _representedMinutes / 60) / 12) * RADIANS];
|
||||
[_minuteHandLayer setRotationRadians:(360 * (_representedMinutes + _representedSeconds / 60) / 60) * RADIANS];
|
||||
[_secondHandLayer setRotationRadians:(360 * _representedSeconds / 60) * RADIANS];
|
||||
|
||||
[_hourHandLayer setNeedsDisplay];
|
||||
[_minuteHandLayer setNeedsDisplay];
|
||||
[_secondHandLayer setNeedsDisplay];
|
||||
// [_middleHandLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Accessors
|
||||
|
||||
- (float)_hourPositionRadianForDate:(CPDate)aDate
|
||||
{
|
||||
var hours = aDate.getHours() + aDate.getMinutes() / 60;
|
||||
|
||||
return (360 * hours / 12) * RADIANS;
|
||||
}
|
||||
|
||||
- (float)_secondPositionRadianForDate:(CPDate)aDate
|
||||
{
|
||||
return (360 * aDate.getSeconds() / 60) * RADIANS;
|
||||
}
|
||||
|
||||
- (float)_minutePositionRadianForDate:(CPDate)aDate
|
||||
{
|
||||
var minutes = aDate.getMinutes() + aDate.getSeconds() / 60;
|
||||
|
||||
return (360 * minutes / 60) * RADIANS;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)shouldEnable
|
||||
{
|
||||
shouldEnable = !!shouldEnable;
|
||||
@@ -205,36 +256,191 @@ var RADIANS = Math.PI / 180;
|
||||
return;
|
||||
|
||||
_isEnabled = shouldEnable;
|
||||
[self setNeedsLayout];
|
||||
|
||||
// FIXME: This is a workaround for an apparent bug in CALayer.
|
||||
// Without pumping the event loop, the sublayers of _rootLayer
|
||||
// (the hands) are not redrawn until an event occurs.
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
[self _initHands];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark Mouse actions
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (!_isEnabled)
|
||||
return;
|
||||
|
||||
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if ([_secondHandLayer handIsHitAtPoint:currentLocation])
|
||||
{
|
||||
_currentHandLayer = _secondHandLayer;
|
||||
_currentHand = _CPDatePickerClockSeconds;
|
||||
_currentRepresentedValue = _representedSeconds;
|
||||
_currentValueShift = 0;
|
||||
_numberOfUnits = 60;
|
||||
}
|
||||
else if ([_minuteHandLayer handIsHitAtPoint:currentLocation])
|
||||
{
|
||||
_currentHandLayer = _minuteHandLayer;
|
||||
_currentHand = _CPDatePickerClockMinutes;
|
||||
_currentRepresentedValue = _representedMinutes;
|
||||
_currentValueShift = _representedSeconds / 60;
|
||||
_numberOfUnits = 60;
|
||||
}
|
||||
else if ([_hourHandLayer handIsHitAtPoint:currentLocation])
|
||||
{
|
||||
_currentHandLayer = _hourHandLayer;
|
||||
_currentHand = _CPDatePickerClockHours;
|
||||
_currentRepresentedValue = _representedHours;
|
||||
_currentValueShift = _representedMinutes / 60;
|
||||
_numberOfUnits = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentHandLayer = nil;
|
||||
_currentHand = CPNotFound;
|
||||
_currentRepresentedValue = CPNotFound;
|
||||
_currentValueShift = CPNotFound;
|
||||
_numberOfUnits = CPNotFound;
|
||||
}
|
||||
|
||||
if (_currentHandLayer)
|
||||
[self trackMouse:anEvent];
|
||||
}
|
||||
|
||||
- (BOOL)tracksMouseOutsideOfFrame
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
_trackingHand = YES;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
|
||||
{
|
||||
var dx = aPoint.x -_bounds.size.width / 2,
|
||||
dy = _bounds.size.height / 2 - aPoint.y,
|
||||
angle = (PI_2 - ATAN2(dy,dx) + PI2) % PI2,
|
||||
value = ROUND(angle * _numberOfUnits / PI2 - _currentValueShift) % _numberOfUnits;
|
||||
|
||||
if (value !== _currentRepresentedValue)
|
||||
{
|
||||
var movedForward = (_currentRepresentedValue > _numberOfUnits * 3/4) && (value < _numberOfUnits / 4),
|
||||
movedBackward = (_currentRepresentedValue < _numberOfUnits / 4) && (value > _numberOfUnits * 3/4),
|
||||
dateValue = [[_datePicker dateValue] copy];
|
||||
|
||||
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
|
||||
|
||||
switch (_currentHand)
|
||||
{
|
||||
case _CPDatePickerClockHours:
|
||||
|
||||
if (movedForward || movedBackward)
|
||||
{
|
||||
_representedHourIsPM = !_representedHourIsPM;
|
||||
|
||||
if (movedForward && !_representedHourIsPM)
|
||||
// Day++
|
||||
dateValue.setDate(dateValue.getDate() + 1);
|
||||
|
||||
else if (movedBackward && _representedHourIsPM)
|
||||
// Day--
|
||||
dateValue.setDate(dateValue.getDate() - 1);
|
||||
}
|
||||
|
||||
dateValue.setHours(value + (_representedHourIsPM ? 12 : 0));
|
||||
break;
|
||||
|
||||
case _CPDatePickerClockMinutes:
|
||||
|
||||
if (movedForward)
|
||||
// Hours++
|
||||
dateValue.setHours(dateValue.getHours() + 1);
|
||||
|
||||
else if (movedBackward)
|
||||
// Hours--
|
||||
dateValue.setHours(dateValue.getHours() - 1);
|
||||
|
||||
dateValue.setMinutes(value);
|
||||
break;
|
||||
|
||||
case _CPDatePickerClockSeconds:
|
||||
|
||||
if (movedForward)
|
||||
// Minutes++
|
||||
dateValue.setMinutes(dateValue.getMinutes() + 1);
|
||||
|
||||
else if (movedBackward)
|
||||
// Minutes--
|
||||
dateValue.setMinutes(dateValue.getMinutes() - 1);
|
||||
|
||||
dateValue.setSeconds(value);
|
||||
break;
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = YES;
|
||||
#endif
|
||||
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = NO;
|
||||
#endif
|
||||
|
||||
// We have to adapt represented values
|
||||
_representedHours = dateValue.getHours();
|
||||
_representedMinutes = dateValue.getMinutes();
|
||||
_representedSeconds = dateValue.getSeconds();
|
||||
_representedHourIsPM = (_representedHours > 11);
|
||||
|
||||
// Hours are expressed in 24 hours format, we need 12 hours format
|
||||
_representedHours -= (_representedHourIsPM ? 12 : 0);
|
||||
|
||||
switch (_currentHand) {
|
||||
case _CPDatePickerClockHours:
|
||||
_currentRepresentedValue = _representedHours;
|
||||
break;
|
||||
|
||||
case _CPDatePickerClockMinutes:
|
||||
_currentRepresentedValue = _representedMinutes;
|
||||
break;
|
||||
|
||||
case _CPDatePickerClockSeconds:
|
||||
_currentRepresentedValue = _representedSeconds;
|
||||
break;
|
||||
}
|
||||
|
||||
[self _updateHands];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
||||
{
|
||||
_trackingHand = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation HandLayer : CALayer
|
||||
{
|
||||
BOOL _isEnabled @accessors(setter=setEnabled:, getter=isEnabled);
|
||||
|
||||
CPImage _image;
|
||||
CALayer _imageLayer;
|
||||
float _rotationRadians;
|
||||
CPImage _image;
|
||||
HandImageLayer _imageLayer;
|
||||
float _rotationRadians;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Init methods
|
||||
|
||||
- (id)initWithSize:(CGSize)aSize
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_isEnabled = YES;
|
||||
_imageLayer = [CALayer layer];
|
||||
_imageLayer = [HandImageLayer layer];
|
||||
_rotationRadians = 0;
|
||||
|
||||
[_imageLayer setDelegate:self];
|
||||
@@ -247,7 +453,6 @@ var RADIANS = Math.PI / 180;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Setter Getter methods
|
||||
|
||||
/*!
|
||||
@@ -285,18 +490,6 @@ var RADIANS = Math.PI / 180;
|
||||
1.0, 1.0)];
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)shouldEnable
|
||||
{
|
||||
shouldEnable = !!shouldEnable;
|
||||
|
||||
if (_isEnabled === shouldEnable)
|
||||
return;
|
||||
|
||||
_isEnabled = shouldEnable;
|
||||
[self setNeedsDisplay];
|
||||
[_imageLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)imageDidLoad:(CPImage)anImage
|
||||
{
|
||||
[_imageLayer setNeedsDisplay];
|
||||
@@ -310,4 +503,109 @@ var RADIANS = Math.PI / 180;
|
||||
CGContextDrawImage(aContext, [aLayer bounds], _image);
|
||||
}
|
||||
|
||||
- (BOOL)handIsHitAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer);
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay
|
||||
{
|
||||
[super setNeedsDisplay];
|
||||
[_imageLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation HandImageLayer : CALayer
|
||||
{
|
||||
CGRect _handBounds;
|
||||
}
|
||||
|
||||
// We have to adapt hitTest so it only takes the hand into account (that's the top half of the image layer)
|
||||
// We are also sure there's no sublayers
|
||||
- (CALayer)hitTest:(CGPoint)aPoint
|
||||
{
|
||||
if (_isHidden)
|
||||
return nil;
|
||||
|
||||
var point = CGPointApplyAffineTransform(aPoint, _transformToLayer);
|
||||
|
||||
return CGRectContainsPoint(_handBounds, point) ? self : nil;
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)aBounds
|
||||
{
|
||||
if (CGRectEqualToRect(_bounds, aBounds))
|
||||
return;
|
||||
|
||||
_handBounds = CGRectMakeCopy(aBounds);
|
||||
|
||||
_handBounds.size.height = _handBounds.size.height / 2;
|
||||
|
||||
[super setBounds:aBounds];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation HoursLayer : CALayer
|
||||
{
|
||||
BOOL _drawsHours;
|
||||
|
||||
CPFont _font @accessors(property=font);
|
||||
CPColor _textColor @accessors(property=textColor);
|
||||
float _radius @accessors(property=radius);
|
||||
}
|
||||
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_drawsHours = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDrawsHours:(BOOL)shouldDrawHours
|
||||
{
|
||||
shouldDrawHours = !!shouldDrawHours;
|
||||
|
||||
if (_drawsHours === shouldDrawHours)
|
||||
return;
|
||||
|
||||
_drawsHours = shouldDrawHours;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawInContext:(CGContext)aContext
|
||||
{
|
||||
[super drawInContext:aContext];
|
||||
|
||||
if (_drawsHours)
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
centerX = bounds.size.width / 2,
|
||||
centerY = bounds.size.height / 2;
|
||||
|
||||
CGContextSelectFont(aContext, _font);
|
||||
CGContextSetFillColor(aContext, _textColor);
|
||||
aContext.textBaseline = @"middle";
|
||||
aContext.textAlign = @"center";
|
||||
|
||||
for (var i = 1, angle = 60.0, x, y; i < 13; i++, angle -= 30.0)
|
||||
{
|
||||
x = centerX + _radius * COS(angle * RADIANS);
|
||||
y = centerY - _radius * SIN(angle * RADIANS);
|
||||
|
||||
aContext.fillText(i, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
/* _CPDatePickerDayView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@import "_CPDatePickerDayViewTextField.j"
|
||||
|
||||
@class CPDatePicker
|
||||
|
||||
@implementation _CPDatePickerDayView : CPControl
|
||||
{
|
||||
CPDate _date @accessors(property=date);
|
||||
|
||||
BOOL _isDisabled;
|
||||
BOOL _isHighlighted;
|
||||
BOOL _isSelected;
|
||||
CPDatePicker _datePicker;
|
||||
CPTextField _textField;
|
||||
|
||||
CPInteger _dayInWeek @accessors(property=dayInWeek);
|
||||
BOOL _firstSelected @accessors(property=firstSelected);
|
||||
BOOL _lastSelected @accessors(property=lastSelected);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Init methods
|
||||
|
||||
/*! Create a new instance of _CPDatePickerDayView
|
||||
@param aFrame
|
||||
@param aDatePicker
|
||||
@return a new instance of _CPDatePickerDayView
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
[self setHitTests:NO];
|
||||
|
||||
_datePicker = aDatePicker;
|
||||
|
||||
// FIXME: Beginning with Aristo3, the text field is directly themed based on a new class _CPDatePickerDayViewTextField
|
||||
if ([self isCSSBased])
|
||||
{
|
||||
_textField = [[_CPDatePickerDayViewTextField alloc] initWithFrame:aFrame];
|
||||
}
|
||||
else
|
||||
{
|
||||
_textField = [[CPTextField alloc] initWithFrame:aFrame];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-vertical-alignment"] forThemeAttribute:@"vertical-alignment"];
|
||||
|
||||
var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"];
|
||||
|
||||
if (contentInset)
|
||||
[_textField setValue:contentInset forThemeAttribute:@"content-inset"];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateSelected] forThemeAttribute:@"font" inState:CPThemeStateSelected];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-color" inState:CPThemeStateSelected];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateHighlighted] forThemeAttribute:@"font" inState:CPThemeStateHighlighted];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateHighlighted];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateHighlighted];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
|
||||
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
|
||||
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
|
||||
}
|
||||
|
||||
[self addSubview:_textField];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Theme methods
|
||||
|
||||
/*! Set a theme
|
||||
*/
|
||||
- (BOOL)setThemeState:(ThemeState)aState
|
||||
{
|
||||
[_textField setThemeState:aState];
|
||||
[super setThemeState:aState];
|
||||
}
|
||||
|
||||
/*! Unset a theme
|
||||
*/
|
||||
- (BOOL)unsetThemeState:(ThemeState)aState
|
||||
{
|
||||
[_textField unsetThemeState:aState];
|
||||
[super unsetThemeState:aState];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getter methods
|
||||
|
||||
/*! Select the tile
|
||||
*/
|
||||
- (void)setSelected:(BOOL)shouldBeSelected
|
||||
{
|
||||
if (_isSelected === shouldBeSelected)
|
||||
return;
|
||||
|
||||
_isSelected = shouldBeSelected;
|
||||
|
||||
if (_isSelected)
|
||||
[self setThemeState:CPThemeStateSelected];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
}
|
||||
|
||||
/*! Disabled the tile (used for previous and next month tile)
|
||||
*/
|
||||
- (void)setDisabled:(BOOL)shouldBeDisabled
|
||||
{
|
||||
if (_isDisabled === shouldBeDisabled)
|
||||
return;
|
||||
|
||||
_isDisabled = shouldBeDisabled;
|
||||
|
||||
if (_isDisabled)
|
||||
[self setThemeState:CPThemeStateDisabled];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateDisabled];
|
||||
}
|
||||
|
||||
- (BOOL)isDisabled
|
||||
{
|
||||
return _isDisabled;
|
||||
}
|
||||
|
||||
/*! Highlight the tile (used for current day)
|
||||
*/
|
||||
- (void)setHighlighted:(BOOL)shouldBeHighlighted
|
||||
{
|
||||
if (_isHighlighted === shouldBeHighlighted)
|
||||
return;
|
||||
|
||||
_isHighlighted = shouldBeHighlighted;
|
||||
|
||||
if (_isHighlighted)
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
/*! Set the stringValue of the tile
|
||||
@param aStringValue
|
||||
*/
|
||||
- (void)setStringValue:(CPString)aStringValue
|
||||
{
|
||||
[_textField setStringValue:aStringValue];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Layout methods
|
||||
|
||||
/*! Layout the subviews
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if ([_datePicker isCSSBased])
|
||||
{
|
||||
var attributeName = @"bezel-color-calendar";
|
||||
|
||||
if (_isSelected)
|
||||
{
|
||||
if (_firstSelected && !_lastSelected)
|
||||
attributeName = @"bezel-color-calendar-left";
|
||||
else if (_lastSelected && !_firstSelected)
|
||||
attributeName = @"bezel-color-calendar-right";
|
||||
else if (!_firstSelected && !_lastSelected)
|
||||
{
|
||||
if (_dayInWeek == 0)
|
||||
attributeName = @"bezel-color-calendar-left";
|
||||
else if (_dayInWeek == 6)
|
||||
attributeName = @"bezel-color-calendar-right";
|
||||
else
|
||||
attributeName = @"bezel-color-calendar-middle";
|
||||
}
|
||||
}
|
||||
|
||||
[self setBackgroundColor:[_datePicker valueForThemeAttribute:attributeName inState:[self themeState]]];
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = [self bounds];
|
||||
[_textField sizeToFit];
|
||||
[_textField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_textField frameSize].width / 2 + [_datePicker valueForThemeAttribute:@"border-width"], bounds.size.height / 2 - [_textField frameSize].height / 2)];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)aFrame
|
||||
{
|
||||
[super setFrame:aFrame];
|
||||
[_textField setFrame:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
|
||||
}
|
||||
|
||||
/*! Drawrect
|
||||
*/
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
[super drawRect:aRect];
|
||||
|
||||
if ([_datePicker isCSSBased])
|
||||
return;
|
||||
|
||||
var themeState = [self themeState],
|
||||
context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
if (themeState.hasThemeState(CPThemeStateSelected))
|
||||
{
|
||||
[self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color-calendar" inState:themeState]];
|
||||
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
|
||||
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:themeState]);
|
||||
CGContextAddRect(context, [self bounds]);
|
||||
CGContextStrokeRect(context, [self bounds]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear color, because the original color of a tile is handle by his superview
|
||||
[self setBackgroundColor:[CPColor clearColor]];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* _CPDatePickerDayViewTextField.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@import "CPTextField.j"
|
||||
|
||||
@implementation _CPDatePickerDayViewTextField : CPTextField
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"datePickerDayViewTextField";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"min-size": CGSizeMakeZero(),
|
||||
@"content-inset": CGInsetMakeZero(),
|
||||
@"text-color": [CPColor blackColor],
|
||||
@"text-shadow-color": [CPColor clearColor],
|
||||
@"text-shadow-offset": CGSizeMakeZero(),
|
||||
@"font": [CPNull null],
|
||||
@"vertical-alignment": CPCenterVerticalTextAlignment,
|
||||
@"alignment": CPCenterTextAlignment,
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
/* _CPDatePickerElementTextField.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPTextField.j"
|
||||
|
||||
@class CPDatePicker
|
||||
@class _CPDatePickerElementView
|
||||
|
||||
CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder";
|
||||
CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification";
|
||||
|
||||
var CPZeroKeyCode = 48,
|
||||
CPNineKeyCode = 57,
|
||||
CPMajAKeyCode = 65,
|
||||
CPMajPKeyCode = 80,
|
||||
CPAKeyCode = 97,
|
||||
CPPKeyCode = 112;
|
||||
|
||||
CPMonthDateType = 0;
|
||||
CPDayDateType = 1;
|
||||
CPYearDateType = 2;
|
||||
CPHourDateType = 3;
|
||||
CPSecondDateType = 4;
|
||||
CPMinuteDateType = 5;
|
||||
CPAMPMDateType = 6;
|
||||
|
||||
/*! An element textField
|
||||
*/
|
||||
@implementation _CPDatePickerElementTextField : CPTextField
|
||||
{
|
||||
_CPDatePickerElementTextField _nextTextField @accessors(property=nextTextField);
|
||||
_CPDatePickerElementTextField _previousTextField @accessors(property=previousTextField);
|
||||
_CPDatePickerElementView _datePickerElementView @accessors(property=datePickerElementView);
|
||||
|
||||
CPDatePicker _datePicker @accessors(setter=setDatePicker:);
|
||||
|
||||
int _dateType @accessors(getter=dateType);
|
||||
int _maxNumber @accessors(getter=maxNumber);
|
||||
int _minNumber @accessors(getter=minNumber);
|
||||
|
||||
BOOL _firstEvent;
|
||||
CPTimer _timerEdition;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"datePickerElementTextField";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
|
||||
@"bezel-color": [CPNull null],
|
||||
@"min-size": CGSizeMakeZero()
|
||||
};
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_firstEvent = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return [_datePicker isEnabled];
|
||||
}
|
||||
|
||||
/*! Set the dateType of the textField
|
||||
*/
|
||||
- (void)setDateType:(int)aDateType
|
||||
{
|
||||
_dateType = aDateType;
|
||||
|
||||
switch (aDateType)
|
||||
{
|
||||
case CPMonthDateType:
|
||||
_minNumber = 1;
|
||||
_maxNumber = 12;
|
||||
break;
|
||||
|
||||
case CPDayDateType:
|
||||
_minNumber = 1;
|
||||
_maxNumber = 31;
|
||||
break;
|
||||
|
||||
case CPYearDateType:
|
||||
_minNumber = 0;
|
||||
_maxNumber = 9999;
|
||||
break;
|
||||
|
||||
case CPHourDateType:
|
||||
_minNumber = 0;
|
||||
_maxNumber = 23;
|
||||
break;
|
||||
|
||||
case CPSecondDateType:
|
||||
_minNumber = 0;
|
||||
_maxNumber = 59;
|
||||
break;
|
||||
|
||||
case CPMinuteDateType:
|
||||
_minNumber = 0;
|
||||
_maxNumber = 59;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Return the maxNumber of the textField
|
||||
*/
|
||||
- (int)maxNumber
|
||||
{
|
||||
if (_dateType == CPDayDateType)
|
||||
return [[_datePicker dateValue] _daysInMonth];
|
||||
|
||||
return _maxNumber;
|
||||
}
|
||||
|
||||
/*! Return the maxNumber of the textField depending of the maxDate
|
||||
*/
|
||||
- (int)_maxNumberWithMaxDate
|
||||
{
|
||||
var maxDate = [_datePicker maxDate],
|
||||
date = [_datePicker dateValue];
|
||||
|
||||
if (maxDate)
|
||||
{
|
||||
switch (_dateType)
|
||||
{
|
||||
case CPMonthDateType:
|
||||
if (maxDate.getFullYear() == date.getFullYear())
|
||||
return maxDate.getMonth();
|
||||
break;
|
||||
|
||||
case CPDayDateType:
|
||||
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth())
|
||||
return maxDate.getDate();
|
||||
break;
|
||||
|
||||
case CPYearDateType:
|
||||
return maxDate.getFullYear();
|
||||
|
||||
case CPHourDateType:
|
||||
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate())
|
||||
return maxDate.getHours();
|
||||
break;
|
||||
|
||||
case CPSecondDateType:
|
||||
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours() && maxDate.getMinutes() == date.getMinutes())
|
||||
return maxDate.getSeconds();
|
||||
break;
|
||||
|
||||
case CPMinuteDateType:
|
||||
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours())
|
||||
return maxDate.getMinutes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _maxNumber;
|
||||
}
|
||||
|
||||
/*! Set the stringValue of the textField. This is going to check if there is 2 or 4 letters. If not it adds a space. Check also the maxDate
|
||||
It's called when the user is editing with the keyboard
|
||||
@param aStringValue a CPString
|
||||
*/
|
||||
- (void)setValueForKeyEvent:(CPEvent)anEvent
|
||||
{
|
||||
var keyCode = [anEvent keyCode];
|
||||
|
||||
if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode)
|
||||
return;
|
||||
|
||||
var newValue = [self stringValue].replace(/\s/g, ''),
|
||||
length = [newValue length],
|
||||
eventKeyValue = parseInt([anEvent characters]).toString();
|
||||
|
||||
if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode)
|
||||
{
|
||||
[_timerEdition invalidate];
|
||||
_timerEdition = nil;
|
||||
newValue = [newValue substringToIndex:(length - 1)];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_timerEdition)
|
||||
{
|
||||
_timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO];
|
||||
|
||||
if (_firstEvent || !length)
|
||||
newValue = eventKeyValue;
|
||||
else
|
||||
newValue = parseInt(newValue).toString() + eventKeyValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newFireDate = [CPDate date];
|
||||
|
||||
newFireDate.setSeconds(newFireDate.getSeconds() + 2);
|
||||
[_timerEdition setFireDate:newFireDate];
|
||||
|
||||
newValue = parseInt(newValue).toString() + eventKeyValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12))
|
||||
return;
|
||||
|
||||
_firstEvent = NO;
|
||||
|
||||
[super setObjectValue:newValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
End of the timer
|
||||
*/
|
||||
- (void)_timerKeyEvent:(id)sender
|
||||
{
|
||||
var stringValue = [self stringValue];
|
||||
|
||||
_timerEdition = nil;
|
||||
|
||||
if ([stringValue length])
|
||||
{
|
||||
if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType)
|
||||
{
|
||||
var isAMHour = [[self superview] _isAMHour];
|
||||
|
||||
if (!isAMHour && stringValue != 12)
|
||||
stringValue = parseInt(stringValue) + 12;
|
||||
|
||||
if (stringValue == 12 && !isAMHour)
|
||||
stringValue = 12;
|
||||
else if (stringValue == 12)
|
||||
stringValue = 0;
|
||||
}
|
||||
|
||||
[self setObjectValue:stringValue];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
We force to end the timer
|
||||
*/
|
||||
- (void)_invalidTimer
|
||||
{
|
||||
if (_timerEdition)
|
||||
{
|
||||
[_timerEdition invalidate];
|
||||
_timerEdition = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
We force to end the timer and to update the objectValue of the datePicker
|
||||
*/
|
||||
- (void)_endEditing
|
||||
{
|
||||
if (_timerEdition)
|
||||
[_timerEdition invalidate];
|
||||
|
||||
_timerEdition = nil;
|
||||
|
||||
var objectValue = [self stringValue];
|
||||
|
||||
if (![objectValue length])
|
||||
objectValue = [self objectValue];
|
||||
|
||||
if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType)
|
||||
{
|
||||
var isAMHour = [[self superview] _isAMHour];
|
||||
|
||||
if (!isAMHour && objectValue != 12)
|
||||
objectValue = parseInt(objectValue) + 12;
|
||||
|
||||
if (objectValue == 12 && !isAMHour)
|
||||
objectValue = 12;
|
||||
else if (objectValue == 12)
|
||||
objectValue = 0;
|
||||
}
|
||||
|
||||
[self setObjectValue:objectValue];
|
||||
}
|
||||
|
||||
/*! Set the stringValue of the TextField. Add some zeros of there isn't 2/4 letters in the value. It's called at the end of the editing process
|
||||
@param aStringValue a CPString
|
||||
*/
|
||||
- (void)setStringValue:(CPString)aStringValue
|
||||
{
|
||||
if (_dateType == CPYearDateType)
|
||||
{
|
||||
while ([aStringValue length] < 4)
|
||||
aStringValue = "0" + aStringValue;
|
||||
}
|
||||
else if (_dateType != CPAMPMDateType)
|
||||
{
|
||||
if (_dateType == CPHourDateType && [_datePicker _isAmericanFormat])
|
||||
{
|
||||
var value = parseInt(aStringValue);
|
||||
|
||||
if (value == 0)
|
||||
value = 12;
|
||||
else if (value > 12)
|
||||
value = value - 12;
|
||||
|
||||
aStringValue = value.toString();
|
||||
}
|
||||
|
||||
while ([aStringValue length] < 2)
|
||||
{
|
||||
if (_dateType == CPSecondDateType || _dateType == CPMinuteDateType)
|
||||
aStringValue = @"0" + aStringValue;
|
||||
else
|
||||
aStringValue = @" " + aStringValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[super setObjectValue:aStringValue];
|
||||
}
|
||||
|
||||
/*! Set the objectValue. This will update the dateValue of the datePicker also. It's called with the binding of the stepper or arrows
|
||||
This is not going to update the objectValue of the control !!! It updates the dateValue of the datePicker who's going to update the datePickerTextField if necessary
|
||||
It's a bit tricky
|
||||
@param aObjectValue
|
||||
*/
|
||||
- (void)setObjectValue:(id)anObjectValue
|
||||
{
|
||||
var dateValue = [[_datePicker dateValue] copy],
|
||||
lengthString = [[self stringValue] length],
|
||||
objectValue = parseInt(anObjectValue);
|
||||
|
||||
switch (_dateType)
|
||||
{
|
||||
case CPMonthDateType:
|
||||
|
||||
if (objectValue == 0 || !lengthString)
|
||||
{
|
||||
[self setStringValue:(dateValue.getMonth() + 1).toString()];
|
||||
return;
|
||||
}
|
||||
|
||||
var dateNextMonth = [dateValue copy];
|
||||
|
||||
dateNextMonth.setDate(1);
|
||||
dateNextMonth.setMonth(parseInt(anObjectValue) - 1);
|
||||
|
||||
var numberDayNextMonth = [dateNextMonth _daysInMonth];
|
||||
|
||||
if (numberDayNextMonth < [dateValue _daysInMonth] && dateValue.getDate() > numberDayNextMonth)
|
||||
[_datePickerElementView setDayDateValue:numberDayNextMonth.toString()];
|
||||
|
||||
[super setObjectValue:objectValue];
|
||||
break;
|
||||
|
||||
case CPDayDateType:
|
||||
|
||||
if (objectValue == 0 || !lengthString)
|
||||
{
|
||||
[self setStringValue:dateValue.getDate().toString()];
|
||||
return;
|
||||
}
|
||||
|
||||
[super setObjectValue:objectValue];
|
||||
break;
|
||||
|
||||
case CPYearDateType:
|
||||
|
||||
if (objectValue == 0 || !lengthString)
|
||||
{
|
||||
[self setStringValue:dateValue.getFullYear().toString()];
|
||||
return;
|
||||
}
|
||||
|
||||
[super setObjectValue:objectValue];
|
||||
break;
|
||||
|
||||
case CPHourDateType:
|
||||
|
||||
if (!lengthString)
|
||||
{
|
||||
[self setStringValue:dateValue.getHours().toString()];
|
||||
return;
|
||||
}
|
||||
|
||||
[super setObjectValue:objectValue];
|
||||
break;
|
||||
|
||||
case CPSecondDateType:
|
||||
|
||||
if (!lengthString)
|
||||
{
|
||||
[self setStringValue:dateValue.getSeconds().toString()];
|
||||
return;
|
||||
}
|
||||
|
||||
[super setObjectValue:objectValue];
|
||||
break;
|
||||
|
||||
case CPMinuteDateType:
|
||||
|
||||
if (!lengthString)
|
||||
{
|
||||
[self setStringValue:dateValue.getMinutes().toString()];
|
||||
return;
|
||||
}
|
||||
|
||||
[super setObjectValue:objectValue];
|
||||
break;
|
||||
}
|
||||
|
||||
var newDateValue = [_datePickerElementView dateValue],
|
||||
timeZone = [_datePicker timeZone];
|
||||
|
||||
if (timeZone)
|
||||
{
|
||||
var secondsFromGMT = [timeZone secondsFromGMTForDate:newDateValue],
|
||||
secondsFromGMTTimeZone = [timeZone secondsFromGMT];
|
||||
|
||||
newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone);
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = YES;
|
||||
#endif
|
||||
[_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]];
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Mouse event
|
||||
|
||||
/*! Mouse down event. Launch a notification to notif the new first responder textField
|
||||
*/
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[super mouseDown:anEvent];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:[[self superview] superview] userInfo:[CPDictionary dictionaryWithObject:self forKey:@"textField"]];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Theme functions
|
||||
|
||||
/*! Set the theme CPThemeStateSelected
|
||||
*/
|
||||
- (void)makeSelectable
|
||||
{
|
||||
[self setThemeState:CPThemeStateSelected];
|
||||
[_datePicker setThemeState:CPThemeStateEditing];
|
||||
}
|
||||
|
||||
/*! Unsert the theme CPThemeStateSelected
|
||||
*/
|
||||
- (void)makeDeselectable
|
||||
{
|
||||
_firstEvent = YES;
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
[_datePicker unsetThemeState:CPThemeStateEditing];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Override
|
||||
|
||||
/*!
|
||||
We override this method to get all the time the good width
|
||||
*/
|
||||
- (CGSize)_minimumFrameSize
|
||||
{
|
||||
var frameSize = [self frameSize],
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"],
|
||||
lineBreakMode = [self lineBreakMode],
|
||||
text = (_dateType == CPYearDateType) ? @"0000" : (_dateType == CPMonthDateType) ? @"10" : @"00",
|
||||
textSize = CGSizeMakeCopy(frameSize),
|
||||
font = [self currentValueForThemeAttribute:@"font"];
|
||||
|
||||
textSize.width -= contentInset.left + contentInset.right;
|
||||
textSize.height -= contentInset.top + contentInset.bottom;
|
||||
|
||||
if (_dateType == CPAMPMDateType)
|
||||
text = [self stringValue];
|
||||
|
||||
if (frameSize.width !== 0 &&
|
||||
![self isBezeled] &&
|
||||
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
|
||||
{
|
||||
textSize = [text sizeWithFont:font inWidth:textSize.width];
|
||||
}
|
||||
else
|
||||
{
|
||||
textSize = [text sizeWithFont:font];
|
||||
|
||||
// Account for possible fractional pixels at right edge
|
||||
textSize.width += 1;
|
||||
}
|
||||
|
||||
// Account for possible fractional pixels at bottom edge
|
||||
textSize.height += 1;
|
||||
|
||||
frameSize.height = textSize.height + contentInset.top + contentInset.bottom;
|
||||
|
||||
if ([self isBezeled])
|
||||
{
|
||||
frameSize.height = MAX(frameSize.height, minSize.height);
|
||||
|
||||
if (maxSize.width > 0.0)
|
||||
frameSize.width = MIN(frameSize.width, maxSize.width);
|
||||
|
||||
if (maxSize.height > 0.0)
|
||||
frameSize.height = MIN(frameSize.height, maxSize.height);
|
||||
}
|
||||
else
|
||||
frameSize.width = textSize.width + contentInset.left + contentInset.right;
|
||||
|
||||
frameSize.width = MAX(frameSize.width, minSize.width);
|
||||
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
- (CGRect)bezelRectForBounds:(CGRect)bounds
|
||||
{
|
||||
return CGRectMakeCopy(bounds);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPDatePickerElementSeparator : CPTextField
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"datePickerElementSeparator";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
|
||||
@"min-size": CGSizeMakeZero()
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,306 @@
|
||||
/* _CPDatePickerHeaderView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPButton.j"
|
||||
|
||||
@class CPDatePicker
|
||||
|
||||
var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"],
|
||||
CPShortWeekDayNameArrayUS = [@"Su", @"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa"],
|
||||
CPShortWeekDayNameArrayFr = [@"L", @"M", @"M", @"J", @"V", @"S", @"D"],
|
||||
CPShortWeekDayNameArrayDe = [@"M", @"D", @"M", @"D", @"F", @"S", @"S"],
|
||||
CPShortWeekDayNameArrayEs = [@"L", @"M", @"X", @"J", @"V", @"S", @"D"],
|
||||
CPShortMonthNameArrayEn = [@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"],
|
||||
CPShortMonthNameArrayFr = [@"janv.", String.fromCharCode(102, 233, 118, 46), @"mars", @"apr.", @"mai", @"juin", @"juil.", String.fromCharCode(97, 111, 251, 116), @"sept.", @"oct.", @"nov.", String.fromCharCode(100, 233, 99, 46)],
|
||||
CPShortMonthNameArrayDe = [@"Jan", @"Feb", String.fromCharCode(77, 228, 114), @"Apr", @"Mai", @"Jun", @"Jul", @"Aug", @"Sep", @"Okt", @"Nov", @"Dez"],
|
||||
CPShortMonthNameArrayEs = [@"ene", @"feb", @"mar", @"abr", @"may", @"jun", @"jul", @"ago", @"sep", @"oct", @"nov", @"dic"];
|
||||
|
||||
@implementation _CPDatePickerHeaderView : CPControl
|
||||
{
|
||||
CPArray _dayLabels;
|
||||
CPArray _monthNames;
|
||||
CPButton _nextButton;
|
||||
CPButton _previousButton;
|
||||
CPButton _currentButton;
|
||||
CPDatePicker _datePicker;
|
||||
CPDate _date;
|
||||
CPTextField _title;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Init methods
|
||||
|
||||
/*! Init a new instance of _CPDatePickerHeaderView
|
||||
@param aFrame
|
||||
@param aDatePicker
|
||||
@return a new instance of _CPDatePickerHeaderView
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_datePicker = aDatePicker;
|
||||
|
||||
// Title
|
||||
_title = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
|
||||
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
|
||||
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
|
||||
|
||||
[self addSubview:_title];
|
||||
|
||||
_dayLabels = [CPArray array];
|
||||
|
||||
// Days
|
||||
for (var i = 0, count = [[self _dayNames] count]; i < count; i++)
|
||||
{
|
||||
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"];
|
||||
|
||||
var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"];
|
||||
|
||||
if (contentInset)
|
||||
[label setValue:contentInset forThemeAttribute:@"content-inset"];
|
||||
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
|
||||
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
|
||||
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
|
||||
|
||||
[_dayLabels addObject:label];
|
||||
[self addSubview:label];
|
||||
}
|
||||
|
||||
// Arrows
|
||||
var size = [_datePicker valueForThemeAttribute:@"previous-button-size"];
|
||||
|
||||
_previousButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
|
||||
[_previousButton setButtonType:CPMomentaryChangeButton];
|
||||
[_previousButton setBordered:NO];
|
||||
[_previousButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-left"]];
|
||||
[_previousButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-left-highlighted"]];
|
||||
[self addSubview:_previousButton];
|
||||
|
||||
size = [_datePicker valueForThemeAttribute:@"next-button-size"];
|
||||
|
||||
_nextButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
|
||||
[_nextButton setButtonType:CPMomentaryChangeButton];
|
||||
[_nextButton setBordered:NO];
|
||||
[_nextButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-right"]];
|
||||
[_nextButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-right-highlighted"]];
|
||||
[self addSubview:_nextButton];
|
||||
|
||||
size = [_datePicker valueForThemeAttribute:@"current-button-size"];
|
||||
|
||||
_currentButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
|
||||
[_currentButton setButtonType:CPMomentaryChangeButton];
|
||||
[_currentButton setBordered:NO];
|
||||
[_currentButton setImage:[_datePicker valueForThemeAttribute:@"circle-image"]];
|
||||
[_currentButton setAlternateImage:[_datePicker valueForThemeAttribute:@"circle-image-highlighted"]];
|
||||
[self addSubview:_currentButton];
|
||||
|
||||
[_previousButton setTarget:aDelegate];
|
||||
[_previousButton setAction:@selector(_clickArrowPrevious:)];
|
||||
[_previousButton setContinuous:YES];
|
||||
|
||||
[_nextButton setTarget:aDelegate];
|
||||
[_nextButton setAction:@selector(_clickArrowNext:)];
|
||||
[_nextButton setContinuous:YES];
|
||||
|
||||
[_currentButton setTarget:aDelegate];
|
||||
[_currentButton setAction:@selector(_currentMonth:)];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getter Setter methods
|
||||
|
||||
/*! Return the day names depending on the CPLocale of the datePicker
|
||||
@return an array
|
||||
*/
|
||||
- (CPArray)_dayNames
|
||||
{
|
||||
switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode])
|
||||
{
|
||||
case @"en":
|
||||
|
||||
// Check if it's in the american format. If yes the week will begin the sunday
|
||||
if ([_datePicker _isAmericanFormat])
|
||||
return CPShortWeekDayNameArrayUS;
|
||||
else
|
||||
return CPShortWeekDayNameArrayEn;
|
||||
break;
|
||||
|
||||
case @"es":
|
||||
return CPShortWeekDayNameArrayEs;
|
||||
break;
|
||||
|
||||
case @"de":
|
||||
return CPShortWeekDayNameArrayDe;
|
||||
break;
|
||||
|
||||
case @"fr":
|
||||
return CPShortWeekDayNameArrayFr;
|
||||
break;
|
||||
|
||||
default:
|
||||
return CPShortWeekDayNameArrayEn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Return the month names depending on the CPLocale of the datePicker
|
||||
@return an array
|
||||
*/
|
||||
- (CPArray)_monthNames
|
||||
{
|
||||
switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode])
|
||||
{
|
||||
case @"en":
|
||||
return CPShortMonthNameArrayEn;
|
||||
break;
|
||||
|
||||
case @"es":
|
||||
return CPShortMonthNameArrayEs;
|
||||
break;
|
||||
|
||||
case @"de":
|
||||
return CPShortMonthNameArrayDe;
|
||||
break;
|
||||
|
||||
case @"fr":
|
||||
return CPShortMonthNameArrayFr;
|
||||
break;
|
||||
|
||||
default:
|
||||
return CPShortMonthNameArrayEn;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*! Set the monthDate of the header
|
||||
@aMonthDate the new monthDate
|
||||
*/
|
||||
- (void)setMonthForDate:(CPDate)aMonthDate
|
||||
{
|
||||
_date = aMonthDate;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*! Set enabled
|
||||
@param aBoolean
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)aBoolean
|
||||
{
|
||||
[_previousButton setEnabled:aBoolean];
|
||||
[_nextButton setEnabled:aBoolean];
|
||||
[_currentButton setEnabled:aBoolean];
|
||||
[_dayLabels makeObjectsPerformSelector:@selector(setEnabled:) withObject:aBoolean];
|
||||
[_title setEnabled:aBoolean];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Layout methods
|
||||
|
||||
/*! Layout the subviews
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
dayNames = [self _dayNames],
|
||||
width = CGRectGetWidth(bounds),
|
||||
buttonInset = [_datePicker valueForThemeAttribute:@"arrow-inset"],
|
||||
numberOfLabels = [_dayLabels count],
|
||||
labelWidth = width / numberOfLabels,
|
||||
sizeButtonLeft = [[_datePicker valueForThemeAttribute:@"arrow-image-left"] size],
|
||||
sizeButtonRight = [[_datePicker valueForThemeAttribute:@"arrow-image-right"] size],
|
||||
sizeButtonCircle = [[_datePicker valueForThemeAttribute:@"circle-image"] size],
|
||||
sizeTileWidth = [_datePicker valueForThemeAttribute:@"size-tile"].width,
|
||||
titleInset = [_datePicker valueForThemeAttribute:@"title-inset"],
|
||||
dayLabelInset = [_datePicker valueForThemeAttribute:@"day-label-inset"];
|
||||
|
||||
// Arrows
|
||||
[_nextButton setFrame:CGRectMake(width - [_nextButton frameSize].width - buttonInset.right, buttonInset.top, sizeButtonRight.width, sizeButtonRight.height)];
|
||||
[_currentButton setFrame:CGRectMake(CGRectGetMinX([_nextButton frame]) - sizeButtonCircle.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonCircle.width, sizeButtonCircle.height)];
|
||||
[_previousButton setFrame:CGRectMake(CGRectGetMinX([_currentButton frame]) - sizeButtonLeft.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonLeft.width, sizeButtonLeft.height)];
|
||||
|
||||
var firstDayTileX;
|
||||
|
||||
// Weekday label
|
||||
for (var i = 0; i < numberOfLabels; i++)
|
||||
{
|
||||
var dayLabel = _dayLabels[i];
|
||||
|
||||
[dayLabel setStringValue:dayNames[i]];
|
||||
|
||||
if (dayLabelInset) // Beginning with Aristo3
|
||||
{
|
||||
var thisWidth = ROUND((i+1) * sizeTileWidth) - ROUND(i * sizeTileWidth);
|
||||
|
||||
[dayLabel sizeToFit];
|
||||
[dayLabel setFrame:CGRectMake(dayLabelInset.left + ROUND(i * sizeTileWidth), dayLabelInset.top, thisWidth, [dayLabel frameSize].height)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[dayLabel sizeToFit];
|
||||
[dayLabel setFrameOrigin:CGPointMake(sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2, 23)];
|
||||
|
||||
if (i == 0)
|
||||
firstDayTileX = sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
[_title setStringValue:[CPString stringWithFormat:@"%s %i", [self _monthNames][_date.getMonth()], _date.getFullYear()]];
|
||||
[_title sizeToFit];
|
||||
|
||||
if (titleInset) // Beginning with Aristo3
|
||||
[_title setFrameOrigin:CGPointMake(titleInset.left, titleInset.top)];
|
||||
else
|
||||
[_title setFrameOrigin:CGPointMake(firstDayTileX, 6)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,636 @@
|
||||
/* _CPDatePickerMonthView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexandre Wilhelm
|
||||
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "_CPDatePickerDayView.j"
|
||||
|
||||
@class CPDatePicker
|
||||
|
||||
@global CPSingleDateMode
|
||||
@global CPRangeDateMode
|
||||
|
||||
@implementation _CPDatePickerMonthView : CPControl
|
||||
{
|
||||
BOOL _isMonthJustChanged;
|
||||
CPArray _dayTiles;
|
||||
CPDate _clickDate;
|
||||
CPDate _dragDate;
|
||||
CPDate _date;
|
||||
CPDate _previousMonth @accessors(property=previousMonth);
|
||||
CPDate _nextMonth @accessors(property=nextMonth);
|
||||
CPDatePicker _datePicker;
|
||||
CPEvent _eventDragged;
|
||||
CPTimer _timerMonth;
|
||||
id _delegate;
|
||||
int _indexDayTile;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Init methods
|
||||
|
||||
/*! Init a _CPDatePickerMonthView
|
||||
@param aFrame
|
||||
@param aDatePicker
|
||||
@return a new _CPDatePickerMonthView
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
_isMonthJustChanged = NO;
|
||||
_indexDayTile = -1;
|
||||
_datePicker = aDatePicker;
|
||||
_dayTiles = [CPArray array];
|
||||
|
||||
// Create tiles
|
||||
for (var i = 0; i < 42; i++)
|
||||
{
|
||||
var dayView = [[_CPDatePickerDayView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker];
|
||||
[self addSubview:dayView];
|
||||
[_dayTiles addObject:dayView];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getter Setter methods
|
||||
|
||||
/*! Set the monthDate of the component
|
||||
@param aDate
|
||||
*/
|
||||
- (void)setMonthForDate:(CPDate)aDate
|
||||
{
|
||||
if (_dragDate)
|
||||
{
|
||||
if (_dragDate.getMonth() != _date.getMonth())
|
||||
_isMonthJustChanged = YES;
|
||||
|
||||
_date = [_dragDate copy];
|
||||
}
|
||||
else
|
||||
{
|
||||
_date = [aDate copy];
|
||||
}
|
||||
|
||||
if (![aDate isEqualToDate:[CPDate distantFuture]])
|
||||
{
|
||||
// Reset the date to the first day of the month & midnight
|
||||
_date.setDate(1);
|
||||
[_date _resetToMidnight];
|
||||
|
||||
// There must be a better way to do this.
|
||||
var firstDay = [_date copy];
|
||||
firstDay.setDate(1);
|
||||
|
||||
// Set the previous and next month date. This is usefull for the tile of the next/previous month
|
||||
_previousMonth = new Date(firstDay.getTime() - 86400000);
|
||||
_previousMonth.setDate(1);
|
||||
|
||||
_nextMonth = new Date(firstDay.getTime() + (([_date _daysInMonth] + 1) * 86400000));
|
||||
_nextMonth.setDate(1);
|
||||
}
|
||||
|
||||
[self reloadData];
|
||||
|
||||
if (_isMonthJustChanged)
|
||||
{
|
||||
var dayTile = [_dayTiles objectAtIndex:_indexDayTile];
|
||||
|
||||
if ([dayTile date].getMonth() == _date.getMonth())
|
||||
{
|
||||
[self mouseDragged:_eventDragged];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([dayTile date].getMonth() - _date.getMonth() == 1 || [dayTile date].getFullYear() - _date.getFullYear() == 1)
|
||||
_timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerNextMonthEvent:) userInfo:nil repeats:NO];
|
||||
else
|
||||
_timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerPreviousMonthEvent:) userInfo:nil repeats:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)monthDate
|
||||
{
|
||||
return _date;
|
||||
}
|
||||
|
||||
/*! Return the size of a tile
|
||||
*/
|
||||
- (CGSize)tileSize
|
||||
{
|
||||
return [_datePicker valueForThemeAttribute:@"size-tile"];
|
||||
}
|
||||
|
||||
/*! Return the first index day of the month
|
||||
*/
|
||||
- (int)startOfWeekForDate:(CPDate)aDate
|
||||
{
|
||||
var day = aDate.getDay();
|
||||
|
||||
// American people begins the week the sunday
|
||||
if (![_datePicker _isAmericanFormat])
|
||||
return (day + 6) % 7;
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
/*! Set enabled
|
||||
@param aBoolean
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)aBoolean
|
||||
{
|
||||
[super setEnabled:aBoolean];
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
|
||||
/*! Return the index of tile depending of the giving event
|
||||
@param anEvent
|
||||
@return an index
|
||||
*/
|
||||
- (CPInteger)indexOfTileForEvent:(CPEvent)anEvent
|
||||
{
|
||||
var locationInView = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
tileSize = [self tileSize],
|
||||
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"],
|
||||
margin = [_datePicker valueForThemeAttribute:@"tile-margin"] || CGSizeMakeZero(),
|
||||
tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"] || CGInsetMakeZero();
|
||||
|
||||
// Get the week row
|
||||
var rowIndex = FLOOR((locationInView.y - tileInset.top - margin.height) / (tileSize.height + 2 * margin.height + borderWidth)),
|
||||
columnIndex = FLOOR((locationInView.x - tileInset.left) / (tileSize.width + 2 * margin.width + borderWidth));
|
||||
|
||||
columnIndex = MIN(MAX(columnIndex, 0), 6);
|
||||
rowIndex = MIN(MAX(rowIndex, 0), 5);
|
||||
|
||||
var tileIndex = (rowIndex * 7) + columnIndex;
|
||||
|
||||
return tileIndex;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Reload data
|
||||
|
||||
/*! Reload the data
|
||||
*/
|
||||
- (void)reloadData
|
||||
{
|
||||
if (!_date)
|
||||
return;
|
||||
|
||||
var currentMonth = _date,
|
||||
startOfMonthDay = [self startOfWeekForDate:currentMonth],
|
||||
daysInPreviousMonth = [_previousMonth _daysInMonth],
|
||||
firstDayToShowInPreviousMonth = daysInPreviousMonth - startOfMonthDay,
|
||||
currentDate = new Date(_previousMonth.getFullYear(), _previousMonth.getMonth(), firstDayToShowInPreviousMonth),
|
||||
now = [CPDate date],
|
||||
dateValue = [_datePicker dateValue];
|
||||
|
||||
// Update the tiles
|
||||
for (var i = 0; i < [_dayTiles count]; i++)
|
||||
{
|
||||
var dayTile = _dayTiles[i];
|
||||
|
||||
// Increment to next day
|
||||
currentDate.setTime(currentDate.getTime() + 90000000);
|
||||
[currentDate _resetToMidnight];
|
||||
|
||||
var isPresentMonth = (now.getMonth() == currentDate.getMonth()
|
||||
&& now.getFullYear() == currentDate.getFullYear());
|
||||
|
||||
[dayTile setDate:[currentDate copy]];
|
||||
[dayTile setStringValue:currentDate.getDate()];
|
||||
[dayTile setDisabled:/*![self isEnabled] ||*/ currentDate.getMonth() !== currentMonth.getMonth() || currentDate < [_datePicker minDate] || currentDate > [_datePicker maxDate]];
|
||||
[dayTile setHighlighted:isPresentMonth && currentDate.getDate() == now.getDate()];
|
||||
}
|
||||
|
||||
// Select the dates
|
||||
[self _selectDate:[_datePicker dateValue] timeInterval:[_datePicker timeInterval]];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Select methods
|
||||
|
||||
/*! Select one date or several date depending of the giving interval
|
||||
@param aStartDate
|
||||
@param anInterval;
|
||||
*/
|
||||
- (void)_selectDate:(CPDate)aStartDate timeInterval:(CPInteger)anInterval
|
||||
{
|
||||
var endDate = [[CPDate alloc] initWithTimeInterval:anInterval sinceDate:aStartDate],
|
||||
tilesCount = [_dayTiles count];
|
||||
|
||||
aStartDate = [aStartDate copy];
|
||||
|
||||
[aStartDate _resetToMidnight];
|
||||
[endDate _resetToMidnight];
|
||||
|
||||
var firstSelected = NO;
|
||||
|
||||
for (var i = 0; i < tilesCount; i++)
|
||||
{
|
||||
var tile = _dayTiles[i],
|
||||
tileDate = [[tile date] copy],
|
||||
selected = NO;
|
||||
|
||||
[tileDate _resetToMidnight];
|
||||
|
||||
if (aStartDate)
|
||||
selected = tileDate >= aStartDate && tileDate <= endDate;
|
||||
|
||||
// Select a tile
|
||||
[tile setSelected:selected];
|
||||
|
||||
// If we are disabled, we have to disable selected tiles so they will appear disabled
|
||||
[tile setDisabled:[tile isDisabled] || (selected && ![self isEnabled])];
|
||||
|
||||
if (selected)
|
||||
{
|
||||
if (!firstSelected)
|
||||
{
|
||||
firstSelected = YES;
|
||||
[tile setFirstSelected:YES];
|
||||
}
|
||||
else
|
||||
[tile setFirstSelected:NO];
|
||||
|
||||
[tile setLastSelected:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (firstSelected)
|
||||
{
|
||||
firstSelected = NO;
|
||||
|
||||
// As there was a first selected and we are now on an unselected tile,
|
||||
// we are sure that i > 0
|
||||
[_dayTiles[i-1] setLastSelected:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Layout methods
|
||||
|
||||
/*! Tile the view
|
||||
*/
|
||||
- (void)tile
|
||||
{
|
||||
var tileSize = [self tileSize],
|
||||
width = tileSize.width,
|
||||
height = tileSize.height,
|
||||
tilesCount = [_dayTiles count],
|
||||
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"],
|
||||
margin = [_datePicker valueForThemeAttribute:@"tile-margin"],
|
||||
tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"],
|
||||
thisWidth,
|
||||
thisX,
|
||||
dayInWeek,
|
||||
weekInMonth,
|
||||
tileFrame,
|
||||
tileIndex;
|
||||
|
||||
// Set the frame of the tiles
|
||||
for (tileIndex = 0; tileIndex < tilesCount; tileIndex++)
|
||||
{
|
||||
dayInWeek = tileIndex % 7;
|
||||
weekInMonth = (tileIndex - dayInWeek) / 7;
|
||||
tileFrame;
|
||||
|
||||
if (margin) // Beginning with Aristo3
|
||||
{
|
||||
thisX = ROUND(dayInWeek * (width + 2 * margin.width));
|
||||
thisWidth = ROUND((dayInWeek+1) * (width + 2 * margin.width)) - thisX;
|
||||
|
||||
tileFrame = CGRectMake(tileInset.left + thisX, tileInset.top + margin.height + weekInMonth * (height + 2 * margin.height), thisWidth + borderWidth, height + borderWidth);
|
||||
}
|
||||
else
|
||||
tileFrame = CGRectMake(dayInWeek * width, weekInMonth * height, width + borderWidth, height + borderWidth);
|
||||
|
||||
[_dayTiles[tileIndex] setFrame:tileFrame];
|
||||
[_dayTiles[tileIndex] setDayInWeek:dayInWeek];
|
||||
}
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
/*! Layout the subviews
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
[self tile];
|
||||
[_dayTiles makeObjectsPerformSelector:@selector(setNeedsLayout)];
|
||||
}
|
||||
|
||||
/*! Draw the component. This draws the border of the tile.
|
||||
The selected tile are drawed in the drawRect method of the tile. But the unselected tile here.
|
||||
It avoids some problems with tiles over other tiles (otherwise the color of the tile border would be different).
|
||||
Rememeber that the first pixel of a tile are over the last pixel of the last tile (because the border)
|
||||
*/
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
[super drawRect:aRect];
|
||||
|
||||
if ([_datePicker isCSSBased])
|
||||
{
|
||||
// We just have to draw the separator (if any)
|
||||
// No separator color means no separator
|
||||
var separatorColor = [_datePicker valueForThemeAttribute:@"separator-color"],
|
||||
separatorHeight = [_datePicker valueForThemeAttribute:@"separator-height"],
|
||||
separatorMarginWidth = [_datePicker valueForThemeAttribute:@"separator-margin-width"];
|
||||
|
||||
if (separatorColor)
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetStrokeColor(context, separatorColor);
|
||||
CGContextSetLineWidth(context, separatorHeight);
|
||||
CGContextMoveToPoint(context, separatorMarginWidth, 0.5);
|
||||
CGContextAddLineToPoint(context, bounds.size.width - separatorMarginWidth, 0.5);
|
||||
CGContextStrokePath(context);
|
||||
CGContextClosePath(context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
width = [self tileSize].width,
|
||||
height = [self tileSize].height,
|
||||
isBorderPair = ([_datePicker valueForThemeAttribute:@"border-width"] % 2) == 0;
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]);
|
||||
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
|
||||
|
||||
if ([_datePicker isBordered])
|
||||
{
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var y = i * height;
|
||||
|
||||
// Very usefull to avoid to have a line of two pixels instead one
|
||||
if (!isBorderPair)
|
||||
y += 0.5;
|
||||
|
||||
CGContextMoveToPoint(context, 0, y);
|
||||
CGContextAddLineToPoint(context, [self bounds].size.width, y);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
var x = i * width;
|
||||
|
||||
// Very usefull to avoid to have a line of two pixels instead one
|
||||
if (!isBorderPair)
|
||||
x += 0.5;
|
||||
|
||||
CGContextMoveToPoint(context, x, 0);
|
||||
CGContextAddLineToPoint(context, x, [self bounds].size.height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var y = 0;
|
||||
|
||||
// Very usefull to avoid to have a line of two pixels instead one
|
||||
if (!isBorderPair)
|
||||
y += 0.5;
|
||||
|
||||
CGContextMoveToPoint(context, 0, y);
|
||||
CGContextAddLineToPoint(context, [self bounds].size.width, y);
|
||||
}
|
||||
|
||||
CGContextStrokePath(context);
|
||||
CGContextClosePath(context);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Mouse event
|
||||
|
||||
/*! Mouse down event
|
||||
*/
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]],
|
||||
dateTile = [[dayTile date] copy],
|
||||
dateValue = [_datePicker dateValue];
|
||||
|
||||
_clickDate = [dateTile copy];
|
||||
_dragDate = nil;
|
||||
_indexDayTile = -1;
|
||||
_eventDragged = nil;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = YES;
|
||||
#endif
|
||||
|
||||
// Check if we have to change or not the month of the component
|
||||
if ([dayTile date].getMonth() == _date.getMonth())
|
||||
{
|
||||
if ([_datePicker datePickerMode] == CPRangeDateMode && [anEvent modifierFlags] & CPShiftKeyMask)
|
||||
{
|
||||
var dateValueAtMidnight = [[_datePicker dateValue] copy];
|
||||
|
||||
[dateValueAtMidnight _resetToMidnight];
|
||||
|
||||
if (dateTile < dateValueAtMidnight)
|
||||
{
|
||||
var interval;
|
||||
|
||||
if (dateTile == dateValueAtMidnight)
|
||||
interval = [_datePicker timeInterval];
|
||||
else
|
||||
interval = ([dateValueAtMidnight timeIntervalSinceDate:dateTile] + [_datePicker timeInterval]);
|
||||
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:interval];
|
||||
}
|
||||
else if ([[dayTile date] isEqualToDate:dateValueAtMidnight])
|
||||
{
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:[dateValueAtMidnight copy]] timeInterval:([dateTile timeIntervalSinceDate:dateValueAtMidnight])];
|
||||
}
|
||||
|
||||
// Be sure to display the good month
|
||||
[_delegate setDateValue:dateTile];
|
||||
}
|
||||
else
|
||||
{
|
||||
var minDate = [[_datePicker minDate] copy],
|
||||
maxDate = [[_datePicker maxDate] copy];
|
||||
|
||||
[minDate _resetToMidnight];
|
||||
[maxDate _resetToLastSeconds];
|
||||
|
||||
if (dateTile >= minDate && dateTile <= maxDate)
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check the year and the month. The year is usefull when changing from Jan to Dec.
|
||||
if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1)
|
||||
[_delegate _displayPreviousMonth];
|
||||
else
|
||||
[_delegate _displayNextMonth];
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*! Mouse dragged event
|
||||
*/
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled] || !CGRectContainsPoint([self bounds],[self convertPoint:[anEvent locationInWindow] fromView:nil]))
|
||||
return;
|
||||
|
||||
var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]],
|
||||
dateTile = [[dayTile date] copy],
|
||||
dateValue = [_datePicker dateValue];
|
||||
|
||||
_dragDate = [dateTile copy];
|
||||
_indexDayTile = [self indexOfTileForEvent:anEvent];
|
||||
_eventDragged = anEvent;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = YES;
|
||||
#endif
|
||||
|
||||
if ([_datePicker datePickerMode] == CPSingleDateMode)
|
||||
{
|
||||
// Check if we have to change or not the month of the component
|
||||
if ([dayTile date].getMonth() == _date.getMonth())
|
||||
{
|
||||
[_timerMonth invalidate];
|
||||
_isMonthJustChanged = NO;
|
||||
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
|
||||
}
|
||||
else if (!_isMonthJustChanged)
|
||||
{
|
||||
[_timerMonth invalidate];
|
||||
_isMonthJustChanged = NO;
|
||||
|
||||
// Check the year and the month. The year is usefull when changing from Jan to Dec.
|
||||
if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1)
|
||||
[_delegate _displayPreviousMonth];
|
||||
else
|
||||
[_delegate _displayNextMonth];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dateTile.getMonth() == _date.getMonth() || !_isMonthJustChanged)
|
||||
{
|
||||
[_timerMonth invalidate];
|
||||
_isMonthJustChanged = NO;
|
||||
|
||||
var dateValueAtMidnight = [[_datePicker dateValue] copy];
|
||||
|
||||
[dateValueAtMidnight _resetToMidnight];
|
||||
|
||||
if (dateTile < _clickDate)
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:[_clickDate timeIntervalSinceDate:dateTile]];
|
||||
else if ([[dayTile date] isEqualToDate:_clickDate])
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
|
||||
else
|
||||
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:_clickDate] timeInterval:[dateTile timeIntervalSinceDate:dateValueAtMidnight]];
|
||||
}
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_datePicker._invokedByUserEvent = NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[_timerMonth invalidate];
|
||||
_dragDate = nil;
|
||||
_clickDate = nil;
|
||||
_isMonthJustChanged = NO;
|
||||
_indexDayTile = -1;
|
||||
_eventDragged = nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Timer
|
||||
|
||||
- (void)_timerNextMonthEvent:(CPEvent)anEvent
|
||||
{
|
||||
if (_isMonthJustChanged)
|
||||
{
|
||||
_dragDate.setMonth(_date.getMonth() + 1);
|
||||
[_delegate _displayNextMonth];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_timerPreviousMonthEvent:(CPEvent)anEvent
|
||||
{
|
||||
if (_isMonthJustChanged)
|
||||
{
|
||||
_dragDate.setMonth(_date.getMonth() - 1);
|
||||
[_delegate _displayPreviousMonth];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Date methods
|
||||
|
||||
- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate
|
||||
{
|
||||
var dateValue = [_datePicker dateValue];
|
||||
|
||||
aDate.setHours(dateValue.getHours());
|
||||
aDate.setMinutes(dateValue.getMinutes());
|
||||
aDate.setSeconds(dateValue.getSeconds());
|
||||
|
||||
return aDate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,7 @@
|
||||
aNewObject._controller = self;
|
||||
aNewObject._key = aKey;
|
||||
|
||||
if (aValue !== nil)
|
||||
if (aValue != nil)
|
||||
[aNewObject setValue:aValue];
|
||||
|
||||
return aNewObject;
|
||||
@@ -98,7 +98,7 @@
|
||||
var iter = [[CPSet setWithArray:allKeys] objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
if (![_excludedKeys containsObject:obj])
|
||||
[array addObject:[self _newObjectWithKey:obj value:nil]];
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ var CPSharedDocumentController = nil;
|
||||
@param aType the type of the new document
|
||||
@param shouldDisplay whether to display the document on screen
|
||||
*/
|
||||
- (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
|
||||
- (CPDocument)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
|
||||
{
|
||||
var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
|
||||
|
||||
@@ -297,7 +297,7 @@ var CPSharedDocumentController = nil;
|
||||
var iter = [_documents objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
{
|
||||
if ([obj isDocumentEdited])
|
||||
return YES;
|
||||
|
||||
+19
-9
@@ -28,7 +28,6 @@
|
||||
|
||||
@import "CPCompatibility.j"
|
||||
@import "CGGeometry.j"
|
||||
@import "CPText.j"
|
||||
@import "CPTrackingArea.j"
|
||||
|
||||
@class CPTextField
|
||||
@@ -36,6 +35,9 @@
|
||||
@class CPGraphicsContext
|
||||
|
||||
@global CPApp
|
||||
@global CPNewlineCharacter
|
||||
@global CPCarriageReturnCharacter
|
||||
@global CPEnterCharacter
|
||||
|
||||
@typedef DOMEvent
|
||||
@typedef CPEventType
|
||||
@@ -64,7 +66,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
CPWindow _window;
|
||||
Number _windowNumber;
|
||||
CPString _characters;
|
||||
CPString _charactersIgnoringModifiers
|
||||
CPString _charactersIgnoringModifiers;
|
||||
BOOL _isARepeat;
|
||||
unsigned _keyCode;
|
||||
DOMEvent _DOMEvent;
|
||||
@@ -83,7 +85,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
BOOL _suppressCappuccinoCut;
|
||||
BOOL _suppressCappuccinoPaste;
|
||||
#endif
|
||||
|
||||
|
||||
CPTrackingArea _trackingArea;
|
||||
}
|
||||
|
||||
@@ -146,7 +148,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
|
||||
/*!
|
||||
Creates a new mouse tracking event.
|
||||
|
||||
|
||||
@param anEventType the event type
|
||||
@param aPoint the location of the cursor in the window specified by \c aWindowNumber
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@@ -232,7 +234,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
{
|
||||
if ((anEventType != CPMouseEntered) && (anEventType != CPMouseExited) && (anEventType != CPCursorUpdate))
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Invalid event type"];
|
||||
|
||||
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CGPointCreateCopy(aPoint);
|
||||
@@ -243,7 +245,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
_trackingArea = aTrackingArea;
|
||||
_window = [CPApp windowWithWindowNumber:aWindowNumber];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -336,6 +338,14 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the subtype of the event.
|
||||
*/
|
||||
- (CPEventType)subtype
|
||||
{
|
||||
return _subtype;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the event's associated window.
|
||||
*/
|
||||
@@ -598,7 +608,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
*/
|
||||
+ (void)stopPeriodicEvents
|
||||
{
|
||||
if (_CPEventPeriodicEventTimer === nil)
|
||||
if (_CPEventPeriodicEventTimer == nil)
|
||||
return;
|
||||
|
||||
window.clearTimeout(_CPEventPeriodicEventTimer);
|
||||
@@ -632,8 +642,8 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
- (CPTrackingArea)trackingArea
|
||||
{
|
||||
if ((_type !== CPMouseEntered) && (_type !== CPMouseExited) && (_type !== CPCursorUpdate))
|
||||
[CPException raise:CPInternalInconsistencyException format:@"You can't call trackingArea for events of type %#x", _type]
|
||||
|
||||
[CPException raise:CPInternalInconsistencyException format:@"You can't call trackingArea for events of type %#x", _type];
|
||||
|
||||
return _trackingArea;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* CPFlashMovie.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPFlashMovie
|
||||
|
||||
CPFlashMovie is used to represent a Flash movie in the Cappuccino framework.
|
||||
*/
|
||||
@implementation CPFlashMovie : CPObject
|
||||
{
|
||||
CPString _filename;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new Flash movie with the swf at \c aFileName.
|
||||
@param aFilename the swf to load
|
||||
@return the initialized CPFlashMovie
|
||||
*/
|
||||
+ (id)flashMovieWithFile:(CPString)aFilename
|
||||
{
|
||||
return [[self alloc] initWithFile:aFilename];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the Flash movie.
|
||||
@param aFilename the swf to load
|
||||
@return the initialized CPFlashMovie
|
||||
*/
|
||||
- (id)initWithFile:(CPString)aFilename
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_filename = aFilename;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)filename
|
||||
{
|
||||
return _filename;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPFlashMovieFilenameKey = "CPFlashMovieFilenameKey";
|
||||
|
||||
@implementation CPFlashMovie (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
_filename = [aCoder decodeObjectForKey:CPFlashMovieFilenameKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_filename forKey:CPFlashMovieFilenameKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* CPFlashView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPFlashMovie.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
@implementation CPFlashView : CPView
|
||||
{
|
||||
CPFlashMovie _flashMovie;
|
||||
|
||||
CPDictionary _params;
|
||||
CPDictionary _paramElements;
|
||||
#if PLATFORM(DOM)
|
||||
DOMElement _DOMParamElement;
|
||||
DOMElement _DOMObjectElement;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_DOMObjectElement = document.createElement(@"object");
|
||||
_DOMObjectElement.id = [self elementID];
|
||||
_DOMObjectElement.width = @"100%";
|
||||
_DOMObjectElement.height = @"100%";
|
||||
_DOMObjectElement.style.top = @"0px";
|
||||
_DOMObjectElement.style.left = @"0px";
|
||||
_DOMObjectElement.type = @"application/x-shockwave-flash";
|
||||
_DOMParamElement = document.createElement(@"param");
|
||||
_DOMParamElement.name = @"movie";
|
||||
|
||||
_DOMObjectElement.appendChild(_DOMParamElement);
|
||||
|
||||
_DOMElement.appendChild(_DOMObjectElement);
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFlashMovie:(CPFlashMovie)aFlashMovie
|
||||
{
|
||||
if (_flashMovie == aFlashMovie)
|
||||
return;
|
||||
|
||||
_flashMovie = aFlashMovie;
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_DOMParamElement.value = [aFlashMovie filename];
|
||||
_DOMObjectElement.data = [aFlashMovie filename];
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPFlashMovie)flashMovie
|
||||
{
|
||||
return _flashMovie;
|
||||
}
|
||||
|
||||
- (void)setFlashVars:(CPDictionary)aDictionary
|
||||
{
|
||||
var varString = @"",
|
||||
enumerator = [aDictionary keyEnumerator],
|
||||
key;
|
||||
|
||||
if (key = [enumerator nextObject])
|
||||
varString = [varString stringByAppendingFormat:@"%@=%@", key, [aDictionary objectForKey:key]];
|
||||
|
||||
while (key = [enumerator nextObject])
|
||||
varString = [varString stringByAppendingFormat:@"&%@=%@", key, [aDictionary objectForKey:key]];
|
||||
|
||||
if (!_params)
|
||||
_params = @{};
|
||||
|
||||
[_params setObject:varString forKey:@"flashvars"];
|
||||
[self setParameters:_params];
|
||||
}
|
||||
|
||||
- (CPDictionary)flashVars
|
||||
{
|
||||
return [_params objectForKey:@"flashvars"];
|
||||
}
|
||||
|
||||
- (void)setParameters:(CPDictionary)aDictionary
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (_paramElements && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
var elements = [_paramElements allValues],
|
||||
count = [elements count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
_DOMObjectElement.removeChild([elements objectAtIndex:i]);
|
||||
}
|
||||
#endif
|
||||
if (!_params)
|
||||
_params = aDictionary;
|
||||
else
|
||||
[_params addEntriesFromDictionary:aDictionary];
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_paramElements = @{};
|
||||
|
||||
var enumerator = [_params keyEnumerator],
|
||||
key;
|
||||
|
||||
while (_DOMObjectElement && (key = [enumerator nextObject]) !== nil)
|
||||
{
|
||||
var param = document.createElement(@"param");
|
||||
param.name = key;
|
||||
param.value = [_params objectForKey:key];
|
||||
|
||||
_DOMObjectElement.appendChild(param);
|
||||
|
||||
[_paramElements setObject:param forKey:key];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPDictionary)parameters
|
||||
{
|
||||
return _params;
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
- (void)_rebuildIEObjects
|
||||
{
|
||||
_DOMElement.innerHTML = @"";
|
||||
if (![_flashMovie filename])
|
||||
return;
|
||||
|
||||
var paramString = [CPString stringWithFormat:@"<param name='movie' value='%@' />", [_flashMovie filename]],
|
||||
paramEnumerator = [_params keyEnumerator],
|
||||
key;
|
||||
|
||||
while ((key = [paramEnumerator nextObject]) !== nil)
|
||||
paramString = [paramString stringByAppendingFormat:@"<param name='%@' value='%@' />", key, [_params objectForKey:key]];
|
||||
|
||||
_DOMObjectElement = document.createElement(@"object");
|
||||
_DOMElement.appendChild(_DOMObjectElement);
|
||||
|
||||
_DOMObjectElement.outerHTML = [CPString stringWithFormat:@"<object id=%@ classid=%@ width=%@ height=%@>%@</object>", [self elementID], IEFlashCLSID, CGRectGetWidth([self bounds]), CGRectGetHeight([self bounds]), paramString];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (CPString)elementID
|
||||
{
|
||||
return @"CPFV_" + [self UID];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(CPEvent)sommit
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
+154
-12
@@ -24,6 +24,9 @@
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPView.j"
|
||||
@import "CPFontDescriptor.j"
|
||||
@import "_CPObject+Theme.j"
|
||||
@import "CPControl.j"
|
||||
|
||||
CPFontDefaultSystemFontFace = @"Arial, sans-serif";
|
||||
CPFontDefaultSystemFontSize = 12;
|
||||
@@ -37,12 +40,15 @@ CPFontCurrentSystemSize = -1;
|
||||
// For internal use only by this class and subclasses
|
||||
_CPFontSystemFacePlaceholder = "_CPFontSystemFacePlaceholder";
|
||||
|
||||
var _CPFontCache = {},
|
||||
_CPSystemFontCache = {},
|
||||
_CPFontSystemFontFace = CPFontDefaultSystemFontFace,
|
||||
_CPFontSystemFontSize = 12,
|
||||
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
|
||||
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g");
|
||||
var _CPFontCache = {},
|
||||
_CPSystemFontCache = {},
|
||||
_CPFontSystemFontFace = CPFontDefaultSystemFontFace,
|
||||
_CPFontSystemFontSize = CPFontDefaultSystemFontSize,
|
||||
_CPFontSystemFontSizeSmall = CPFontDefaultSystemFontSize - 1,
|
||||
_CPFontSystemFontSizeMini = CPFontDefaultSystemFontSize - 2,
|
||||
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
|
||||
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g"),
|
||||
_CPFontSystemFontFaceSpecified = NO;
|
||||
|
||||
|
||||
#define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize)
|
||||
@@ -108,7 +114,7 @@ following:
|
||||
<string>Asap</string>
|
||||
@endcode
|
||||
*/
|
||||
@implementation CPFont : CPObject
|
||||
@implementation CPFont : CPObject <CPTheme>
|
||||
{
|
||||
CPString _name;
|
||||
float _size;
|
||||
@@ -122,6 +128,22 @@ following:
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"font";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"system-font-face": [CPNull null],
|
||||
@"system-font-style": [CPNull null],
|
||||
@"system-font-size-regular": [CPNull null],
|
||||
@"system-font-size-small": [CPNull null],
|
||||
@"system-font-size-mini": [CPNull null]
|
||||
};
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPFont class])
|
||||
@@ -133,7 +155,10 @@ following:
|
||||
systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"];
|
||||
|
||||
if (systemFontFace)
|
||||
{
|
||||
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
|
||||
_CPFontSystemFontFaceSpecified = YES;
|
||||
}
|
||||
|
||||
var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"];
|
||||
|
||||
@@ -141,7 +166,62 @@ following:
|
||||
systemFontSize = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontSize"];
|
||||
|
||||
if (systemFontSize)
|
||||
{
|
||||
_CPFontSystemFontSize = systemFontSize;
|
||||
_CPFontSystemFontFaceSpecified = YES;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)initializeSystemFontFromTheme:(CPTheme)aTheme
|
||||
{
|
||||
// If something was specified via +initialize (from an Info.plist file), don't do anything
|
||||
if (_CPFontSystemFontFaceSpecified)
|
||||
return;
|
||||
|
||||
// Reset all system font caches
|
||||
_CPSystemFontCache = {};
|
||||
|
||||
// Now, try to get information from the theme
|
||||
var systemFontFace = [aTheme valueForAttributeWithName:@"system-font-face" forClass:[CPFont class]];
|
||||
|
||||
if (systemFontFace)
|
||||
{
|
||||
[self _invalidateSystemFontCache];
|
||||
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
|
||||
}
|
||||
|
||||
var systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-regular" forClass:[CPFont class]];
|
||||
|
||||
if (systemFontSize)
|
||||
{
|
||||
[self _invalidateSystemFontCache];
|
||||
_CPFontSystemFontSize = systemFontSize;
|
||||
}
|
||||
|
||||
systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-small" forClass:[CPFont class]];
|
||||
|
||||
if (systemFontSize)
|
||||
{
|
||||
[self _invalidateSystemFontCache];
|
||||
_CPFontSystemFontSizeSmall = systemFontSize;
|
||||
}
|
||||
|
||||
systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-mini" forClass:[CPFont class]];
|
||||
|
||||
if (systemFontSize)
|
||||
{
|
||||
[self _invalidateSystemFontCache];
|
||||
_CPFontSystemFontSizeMini = systemFontSize;
|
||||
}
|
||||
|
||||
// Is there something to add to the global syle definition ?
|
||||
var systemFontStyle = [aTheme valueForAttributeWithName:@"system-font-style" forClass:[CPFont class]];
|
||||
|
||||
if (systemFontStyle)
|
||||
{
|
||||
// Yes, so install it in the DOM Style element
|
||||
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPathInString:systemFontStyle];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -162,7 +242,7 @@ following:
|
||||
if (normalizedFaces === _CPFontSystemFontFace)
|
||||
return;
|
||||
|
||||
[self _invalidateSystemFontCache]
|
||||
[self _invalidateSystemFontCache];
|
||||
_CPFontSystemFontFace = aFace;
|
||||
}
|
||||
|
||||
@@ -176,14 +256,13 @@ following:
|
||||
|
||||
+ (CPFont)systemFontForControlSize:(CPControlSize)aSize
|
||||
{
|
||||
// TODO These sizes should be themable or made less arbitrary in some other way.
|
||||
switch (aSize)
|
||||
{
|
||||
case CPSmallControlSize:
|
||||
return [self systemFontOfSize:_CPFontSystemFontSize - 1];
|
||||
return [self systemFontOfSize:_CPFontSystemFontSizeSmall];
|
||||
|
||||
case CPMiniControlSize:
|
||||
return [self systemFontOfSize:_CPFontSystemFontSize - 2];
|
||||
return [self systemFontOfSize:_CPFontSystemFontSizeMini];
|
||||
|
||||
case CPRegularControlSize:
|
||||
default:
|
||||
@@ -314,6 +393,10 @@ following:
|
||||
_isItalic = isItalic;
|
||||
_isSystem = isSystem;
|
||||
|
||||
_theme = [CPTheme defaultTheme];
|
||||
_themeState = CPThemeStateNormal;
|
||||
[self _loadThemeAttributes];
|
||||
|
||||
if (isSystem)
|
||||
{
|
||||
_name = aName;
|
||||
@@ -431,8 +514,61 @@ following:
|
||||
_lineHeight = [metrics objectForKey:@"lineHeight"];
|
||||
}
|
||||
|
||||
- (CPControlSize)controlSizeCorrespondingToFontSize
|
||||
{
|
||||
switch (_size)
|
||||
{
|
||||
case _CPFontSystemFontSizeSmall:
|
||||
return CPSmallControlSize;
|
||||
|
||||
case _CPFontSystemFontSizeMini:
|
||||
return CPMiniControlSize;
|
||||
|
||||
default:
|
||||
// If we can't find a corresponding size, return regular control size
|
||||
return CPRegularControlSize;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPFont(DescriptorAdditions)
|
||||
|
||||
- (id)_initWithFontDescriptor:(CPFontDescriptor)fontDescriptor
|
||||
{
|
||||
var aName = [fontDescriptor objectForKey: CPFontNameAttribute] ,
|
||||
aSize = [fontDescriptor pointSize],
|
||||
isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait,
|
||||
isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait;
|
||||
|
||||
return [self _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO];
|
||||
}
|
||||
|
||||
+ (CPFont)fontWithDescriptor:(CPFontDescriptor)fontDescriptor size:(float)aSize
|
||||
{
|
||||
var aName = [fontDescriptor objectForKey: CPFontNameAttribute],
|
||||
isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait,
|
||||
isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait;
|
||||
|
||||
return [self _fontWithName:aName size:aSize || [fontDescriptor pointSize] bold:isBold italic:isItalic];
|
||||
}
|
||||
|
||||
- (CPFontDescriptor)fontDescriptor
|
||||
{
|
||||
var traits = 0;
|
||||
|
||||
if ([self isBold])
|
||||
traits |= CPFontBoldTrait;
|
||||
|
||||
if ([self isItalic])
|
||||
traits |= CPFontItalicTrait;
|
||||
|
||||
return [[CPFontDescriptor fontDescriptorWithName:_name size:_size] fontDescriptorWithSymbolicTraits:traits];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPFontNameKey = @"CPFontNameKey",
|
||||
CPFontSizeKey = @"CPFontSizeKey",
|
||||
CPFontIsBoldKey = @"CPFontIsBoldKey",
|
||||
@@ -454,7 +590,11 @@ var CPFontNameKey = @"CPFontNameKey",
|
||||
isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey],
|
||||
isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey];
|
||||
|
||||
return [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
|
||||
self = [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
|
||||
|
||||
[self _decodeThemeObjectsWithCoder:aCoder];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -468,6 +608,8 @@ var CPFontNameKey = @"CPFontNameKey",
|
||||
[aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
|
||||
[aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
|
||||
[aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
|
||||
|
||||
[self _encodeThemeObjectsWithCoder:aCoder];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+213
-31
@@ -22,9 +22,14 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPFontDescriptor.j"
|
||||
|
||||
@global CPApp
|
||||
@class CPFontPanel
|
||||
|
||||
@global document
|
||||
|
||||
CPItalicFontMask = 1 << 0;
|
||||
CPBoldFontMask = 1 << 1;
|
||||
@@ -41,7 +46,20 @@ CPUnitalicFontMask = 1 << 24;
|
||||
|
||||
|
||||
var CPSharedFontManager = nil,
|
||||
CPFontManagerFactory = Nil;
|
||||
CPFontManagerFactory = nil,
|
||||
CPFontPanelFactory = nil;
|
||||
|
||||
/*
|
||||
modifyFont: sender's tag
|
||||
*/
|
||||
CPNoFontChangeAction = 0;
|
||||
CPViaPanelFontAction = 1;
|
||||
CPAddTraitFontAction = 2;
|
||||
CPSizeUpFontAction = 3;
|
||||
CPSizeDownFontAction = 4;
|
||||
CPHeavierFontAction = 5;
|
||||
CPLighterFontAction = 6;
|
||||
CPRemoveTraitFontAction = 7;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -59,6 +77,8 @@ var CPSharedFontManager = nil,
|
||||
BOOL _multiple @accessors(getter=isMultiple, setter=setMultiple:);
|
||||
|
||||
CPDictionary _activeChange;
|
||||
|
||||
unsigned _fontAction;
|
||||
}
|
||||
|
||||
// Getting the Shared Font Manager
|
||||
@@ -83,6 +103,15 @@ var CPSharedFontManager = nil,
|
||||
{
|
||||
CPFontManagerFactory = aClass;
|
||||
}
|
||||
/*!
|
||||
Sets the class that will be used to create the application's
|
||||
Font panel.
|
||||
*/
|
||||
+ (void)setFontPanelFactory:(Class)aClass
|
||||
{
|
||||
CPFontPanelFactory = aClass;
|
||||
}
|
||||
|
||||
|
||||
- (id)init
|
||||
{
|
||||
@@ -163,35 +192,6 @@ var CPSharedFontManager = nil,
|
||||
return ([aFont isBold] ? CPBoldFontMask : 0) | ([aFont isItalic] ? CPItalicFontMask : 0);
|
||||
}
|
||||
|
||||
- (CPFont)convertFont:(CPFont)aFont
|
||||
{
|
||||
if (!_activeChange)
|
||||
return aFont;
|
||||
|
||||
var addTraits = [_activeChange valueForKey:@"addTraits"];
|
||||
|
||||
if (addTraits)
|
||||
aFont = [self convertFont:aFont toHaveTrait:addTraits];
|
||||
|
||||
return aFont;
|
||||
}
|
||||
|
||||
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)addTraits
|
||||
{
|
||||
if (!aFont)
|
||||
return nil;
|
||||
|
||||
var shouldBeBold = ([aFont isBold] || (addTraits & CPBoldFontMask)) && !(addTraits & CPUnboldFontMask),
|
||||
shouldBeItalic = ([aFont isItalic] || (addTraits & CPItalicFontMask)) && !(addTraits & CPUnitalicFontMask),
|
||||
shouldBeSize = [aFont size];
|
||||
|
||||
// XXX On the current platform there will always be a bold/italic version of each font, but still leave
|
||||
// || aFont in here for future platforms.
|
||||
aFont = [CPFont _fontWithName:[aFont familyName] size:shouldBeSize bold:shouldBeBold italic:shouldBeItalic] || aFont;
|
||||
|
||||
return aFont;
|
||||
}
|
||||
|
||||
- (CPFont)convertFont:(CPFont)aFont toFace:(CPString)aTypeface
|
||||
{
|
||||
if (!aFont)
|
||||
@@ -209,7 +209,8 @@ var CPSharedFontManager = nil,
|
||||
- (@action)addFontTrait:(id)sender
|
||||
{
|
||||
var tag = [sender tag];
|
||||
_activeChange = tag === nil ? @{} : @{ @"addTraits": tag };
|
||||
_activeChange = tag == nil ? @{} : @{ @"addTraits": tag };
|
||||
_fontAction = CPAddTraitFontAction;
|
||||
|
||||
[self sendAction];
|
||||
}
|
||||
@@ -219,6 +220,187 @@ var CPSharedFontManager = nil,
|
||||
return [CPApp sendAction:_action to:_target from:self];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
This method open the font panel, create it if necessary.
|
||||
@param sender The object that sent the message.
|
||||
*/
|
||||
- (CPFontPanel)fontPanel:(BOOL)createIt
|
||||
{
|
||||
var panel = nil,
|
||||
panelExists = [CPFontPanelFactory sharedFontPanelExists];
|
||||
|
||||
if ((panelExists) || (!panelExists && createIt))
|
||||
panel = [CPFontPanelFactory sharedFontPanel];
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font to have the specified Font traits. The font is unchanged expect for the specified Font traits.
|
||||
Using CPUnboldFontMask or CPUnitalicFontMask will respectively remove Bold and Italic traits.
|
||||
@param aFont The font to convert.
|
||||
@param fontTrait The new font traits mask.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)fontTrait
|
||||
{
|
||||
var attributes = [[[aFont fontDescriptor] fontAttributes] copy],
|
||||
symbolicTrait = [[aFont fontDescriptor] symbolicTraits];
|
||||
|
||||
if (fontTrait & CPBoldFontMask)
|
||||
symbolicTrait |= CPFontBoldTrait;
|
||||
|
||||
if (fontTrait & CPItalicFontMask)
|
||||
symbolicTrait |= CPFontItalicTrait;
|
||||
|
||||
if (fontTrait & CPUnboldFontMask) /* FIXME: this only change CPFontSymbolicTrait what about CPFontWeightTrait */
|
||||
symbolicTrait &= ~CPFontBoldTrait;
|
||||
|
||||
if (fontTrait & CPUnitalicFontMask)
|
||||
symbolicTrait &= ~CPFontItalicTrait;
|
||||
|
||||
if (fontTrait & CPExpandedFontMask)
|
||||
symbolicTrait |= CPFontExpandedTrait;
|
||||
|
||||
if (fontTrait & CPSmallCapsFontMask)
|
||||
symbolicTrait |= CPFontSmallCapsTrait;
|
||||
|
||||
if (![attributes containsKey:CPFontTraitsAttribute])
|
||||
[attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait]
|
||||
forKey:CPFontTraitsAttribute];
|
||||
else
|
||||
[[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait];
|
||||
|
||||
return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font to not have the specified Font traits. The font is unchanged expect for the specified Font traits.
|
||||
@param aFont The font to convert.
|
||||
@param fontTrait The font traits mask to remove.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont toNotHaveTrait:(CPFontTraitMask)fontTrait
|
||||
{
|
||||
var attributes = [[[aFont fontDescriptor] fontAttributes] copy],
|
||||
symbolicTrait = [[aFont fontDescriptor] symbolicTraits];
|
||||
|
||||
if ((fontTrait & CPBoldFontMask) || (fontTrait & CPUnboldFontMask)) /* FIXME: see convertFont:toHaveTrait: about CPFontWeightTrait */
|
||||
symbolicTrait &= ~CPFontBoldTrait;
|
||||
|
||||
if ((fontTrait & CPItalicFontMask) || (fontTrait & CPUnitalicFontMask))
|
||||
symbolicTrait &= ~CPFontItalicTrait;
|
||||
|
||||
if (fontTrait & CPExpandedFontMask)
|
||||
symbolicTrait &= ~CPFontExpandedTrait;
|
||||
|
||||
if (fontTrait & CPSmallCapsFontMask)
|
||||
symbolicTrait &= ~CPFontSmallCapsTrait;
|
||||
|
||||
if (![attributes containsKey:CPFontTraitsAttribute])
|
||||
[attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait]
|
||||
forKey:CPFontTraitsAttribute];
|
||||
else
|
||||
[[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait];
|
||||
|
||||
return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font to have specified size. The font is unchanged expect for the specified size.
|
||||
@param aFont The font to convert.
|
||||
@param aSize The new font size.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont toSize:(float)aSize
|
||||
{
|
||||
var descriptor = [aFont fontDescriptor];
|
||||
|
||||
return [[aFont class] fontWithDescriptor: descriptor size:aSize]
|
||||
}
|
||||
|
||||
- (void)orderFrontFontPanel:(id)sender
|
||||
{
|
||||
[[self fontPanel:YES] orderFront:sender];
|
||||
}
|
||||
|
||||
- (void)modifyFont:(id)sender
|
||||
{
|
||||
_fontAction = [sender tag];
|
||||
[self sendAction];
|
||||
|
||||
if (_selectedFont)
|
||||
[self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
This method causes the receiver to send its action message.
|
||||
@param sender The object that sent the message. (a Font panel)
|
||||
*/
|
||||
- (void)modifyFontViaPanel:(id)sender
|
||||
{
|
||||
_fontAction = CPViaPanelFontAction;
|
||||
if (_selectedFont)
|
||||
[self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO];
|
||||
|
||||
[self sendAction];
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font according to current font changes, provided by the object that initiated the font change.
|
||||
@param aFont The font to convert.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont
|
||||
{
|
||||
var newFont = nil;
|
||||
|
||||
switch (_fontAction)
|
||||
{
|
||||
case CPNoFontChangeAction:
|
||||
newFont = aFont;
|
||||
break;
|
||||
|
||||
case CPViaPanelFontAction:
|
||||
newFont = [[self fontPanel:NO] panelConvertFont:aFont];
|
||||
break;
|
||||
|
||||
case CPAddTraitFontAction:
|
||||
newFont = aFont;
|
||||
|
||||
if (!_activeChange)
|
||||
break;
|
||||
|
||||
var addTraits = [_activeChange valueForKey:@"addTraits"];
|
||||
|
||||
if (addTraits)
|
||||
newFont = [self convertFont:aFont toHaveTrait:addTraits];
|
||||
break;
|
||||
|
||||
case CPSizeUpFontAction:
|
||||
newFont = [self convertFont:aFont toSize:[aFont size] + 1.0]; /* any limit ? */
|
||||
break;
|
||||
|
||||
case CPSizeDownFontAction:
|
||||
if ([aFont size] > 1)
|
||||
newFont = [self convertFont:aFont toSize:[aFont size] - 1.0];
|
||||
/* else CPBeep() :-p */
|
||||
break;
|
||||
|
||||
default:
|
||||
CPLog.trace(@"-[" + [self className] + " " + _cmd + "] unsupported font action: " + _fontAction + " aFont unchanged");
|
||||
newFont = aFont;
|
||||
break;
|
||||
}
|
||||
|
||||
return newFont;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPFontDetectSpan,
|
||||
|
||||
@@ -92,7 +92,7 @@ var CPGraphicsContextCurrent = nil,
|
||||
@param aGraphicsPort the graphics port to initialize with
|
||||
@return the initialized context
|
||||
*/
|
||||
- (id)initWithGraphicsPort:(CPContext)aGraphicsPort
|
||||
- (id)initWithGraphicsPort:(CGContext)aGraphicsPort
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
|
||||
+420
-6
@@ -26,10 +26,14 @@
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPData.j>
|
||||
@import <Foundation/CPKeyedArchiver.j>
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
@class CPColor
|
||||
@global document
|
||||
|
||||
@protocol CPImageDelegate <CPObject>
|
||||
|
||||
@@ -85,9 +89,7 @@ function CPImageInBundle()
|
||||
|
||||
if (typeof(arguments[1]) === "number")
|
||||
{
|
||||
if (arguments[1] !== nil && arguments[1] !== undefined)
|
||||
size = CGSizeMake(arguments[1], arguments[2]);
|
||||
|
||||
size = CGSizeMake(arguments[1], arguments[2]);
|
||||
bundle = arguments[3];
|
||||
}
|
||||
else if (typeof(arguments[1]) === "object")
|
||||
@@ -161,7 +163,7 @@ function CPAppKitImage(aFilename, aSize)
|
||||
- (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
|
||||
{
|
||||
// Quietly return nil like in Cocoa, rather than crashing later.
|
||||
if (aFilename === undefined || aFilename === nil)
|
||||
if (aFilename == nil)
|
||||
return nil;
|
||||
|
||||
self = [super init];
|
||||
@@ -246,7 +248,7 @@ function CPAppKitImage(aFilename, aSize)
|
||||
var canvas = document.createElement("canvas"),
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
canvas.width = _image.width,
|
||||
canvas.width = _image.width;
|
||||
canvas.height = _image.height;
|
||||
|
||||
ctx.drawImage(_image, 0, 0);
|
||||
@@ -446,6 +448,11 @@ function CPAppKitImage(aFilename, aSize)
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isMaterialIconImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
var filename = [self filename],
|
||||
@@ -509,6 +516,229 @@ function CPAppKitImage(aFilename, aSize)
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CSS Theming
|
||||
|
||||
// The code below adds support for CSS theming with 100% compatibility with current theming system.
|
||||
// The idea is to extend CPImage (and CPColor) with CSS components and adapt low level UI components to
|
||||
// support this new kind of CPColor/CPImage. See CPImageView, CPView and _CPImageAndTextView.
|
||||
//
|
||||
// To create a CPImage that uses CSS, simply use the new class method :
|
||||
// + (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
|
||||
// where beforeDictionary & afterDictionary are related to ::before & ::after pseudo-elements.
|
||||
// If you don't need them, just use the simplified class method :
|
||||
// + (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize
|
||||
//
|
||||
// Examples :
|
||||
// regularImageNormal = [CPImage imageWithCSSDictionary:@{
|
||||
// @"border-color": A3ColorActiveBorder,
|
||||
// @"border-style": @"solid",
|
||||
// @"border-width": @"1px",
|
||||
// @"border-radius": @"50%",
|
||||
// @"box-sizing": @"border-box",
|
||||
// @"background-color": A3ColorBackgroundWhite,
|
||||
// @"transition-duration": @"0.35s",
|
||||
// @"transition-property": @"all",
|
||||
// @"transition-timing-function": @"ease"
|
||||
// }
|
||||
// size:CGSizeMake(16,16)];
|
||||
//
|
||||
// imageSearch = [CPImage imageWithCSSDictionary:@{
|
||||
// @"background-image": @"url(%%packed.png)",
|
||||
// @"background-position": @"-16px -32px",
|
||||
// @"background-repeat": @"no-repeat",
|
||||
// @"background-size": @"100px 400px"
|
||||
// }
|
||||
// size:CGSizeMake(16,16)];
|
||||
//
|
||||
// Remark : Please note the special URL of the background image used in this example : url(%%packed.png)
|
||||
// During theme loading, "%%" will be replaced by the path to the theme blend resources folder.
|
||||
// Typically, a CSS theme will use some (rare) images all packed together in a single image resource (see packed.png in Aristo3 theme)
|
||||
//
|
||||
// Also, please note that if you don't use one of the CSS components, you can either set it to nil (best solution) or to an empty dictionary, like :
|
||||
// aCssImage = [CPImage imageWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:@{ ... }];
|
||||
//
|
||||
// You can use -(BOOL)isCSSBased to determine how to cope with it in your code.
|
||||
// -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use.
|
||||
//
|
||||
// Remark : -(DOMElement)applyCSSImageForView is meant to be used by low level UI widgets (like CPImageView and _CPImageAndTextView) to implement CSS theme support.
|
||||
//
|
||||
// In some circumstances, you may have to clear a CSS image. You can do this easily by replacing your current image with the special dummy empty CSS image :
|
||||
// [CPImage dummyCSSImageOfSize:CGSizeMake(someWidth, someHeight)]
|
||||
|
||||
@implementation CPImage (CSSTheming)
|
||||
{
|
||||
CPDictionary _cssDictionary @accessors(property=cssDictionary);
|
||||
CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary);
|
||||
CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary);
|
||||
CGSize _displaySize;
|
||||
}
|
||||
|
||||
+ (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize
|
||||
{
|
||||
return [[CPImage alloc] initWithCSSDictionary:aDictionary beforeDictionary:nil afterDictionary:nil size:aSize];
|
||||
}
|
||||
|
||||
+ (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
|
||||
{
|
||||
return [[CPImage alloc] initWithCSSDictionary:aDictionary beforeDictionary:beforeDictionary afterDictionary:afterDictionary size:aSize];
|
||||
}
|
||||
|
||||
+ (CPImage)dummyCSSImageOfSize:(CGSize)aSize
|
||||
{
|
||||
// This is used to clear a previous CSS image
|
||||
return [[CPImage alloc] initWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:nil size:aSize];
|
||||
}
|
||||
|
||||
+ (CPImage)imageWithMaterialIconNamed:(CPString)iconName size:(CGSize)size
|
||||
{
|
||||
return [_CPMaterialIconImage imageWithIconNamed:iconName size:size];
|
||||
}
|
||||
|
||||
+ (CPImage)imageWithMaterialIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color
|
||||
{
|
||||
return [_CPMaterialIconImage imageWithIconNamed:iconName size:size color:color];
|
||||
}
|
||||
|
||||
- (id)initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = CGSizeMakeCopy(aSize);
|
||||
_filename = @"CSS image";
|
||||
_loadStatus = CPImageLoadStatusCompleted;
|
||||
_cssDictionary = aDictionary;
|
||||
_cssBeforeDictionary = beforeDictionary;
|
||||
_cssAfterDictionary = afterDictionary;
|
||||
_displaySize = CGSizeMakeCopy(aSize);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isCSSBased
|
||||
{
|
||||
return !!(_cssDictionary || _cssBeforeDictionary || _cssAfterDictionary);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSDictionary
|
||||
{
|
||||
return ([_cssDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSBeforeDictionary
|
||||
{
|
||||
return ([_cssBeforeDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSAfterDictionary
|
||||
{
|
||||
return ([_cssAfterDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (DOMElement)applyCSSImageForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
// First, restore previous CSS styling before applying the new one
|
||||
|
||||
var aPreviousState = @deref(aPreviousStateRef);
|
||||
|
||||
for (var i = 0, count = aPreviousState.length; i < count; i++)
|
||||
aDOMElement.style[aPreviousState[i][0]] = aPreviousState[i][1];
|
||||
|
||||
aPreviousState = @[];
|
||||
|
||||
// Then apply new CSS styling
|
||||
|
||||
[_cssDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[aPreviousState addObject:@[aKey, aDOMElement.style[aKey]]];
|
||||
aDOMElement.style[aKey] = anObject;
|
||||
}];
|
||||
|
||||
if (_cssBeforeDictionary || _cssAfterDictionary)
|
||||
{
|
||||
// We need to create a unique class name
|
||||
|
||||
var styleClassName = @".CP" + [aView UID],
|
||||
styleContent = @"";
|
||||
|
||||
if (_cssBeforeDictionary)
|
||||
{
|
||||
styleContent += styleClassName + @"::before { ";
|
||||
|
||||
[_cssBeforeDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
styleContent += aKey + ": " + anObject + "; ";
|
||||
}];
|
||||
|
||||
styleContent += "} ";
|
||||
}
|
||||
|
||||
if (_cssAfterDictionary)
|
||||
{
|
||||
styleContent += styleClassName + @"::after { ";
|
||||
|
||||
[_cssAfterDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
styleContent += aKey + ": " + anObject + "; ";
|
||||
}];
|
||||
|
||||
styleContent += "} ";
|
||||
}
|
||||
|
||||
var styleDescription = document.createTextNode(styleContent);
|
||||
|
||||
if (!aStyleNode)
|
||||
{
|
||||
aStyleNode = document.createElement("style");
|
||||
|
||||
aView._DOMElement.insertBefore(aStyleNode, aView._DOMElement.firstChild);
|
||||
|
||||
aStyleNode.appendChild(styleDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
|
||||
}
|
||||
|
||||
aDOMElement.className = @"CP"+[aView UID];
|
||||
}
|
||||
else
|
||||
{
|
||||
// no before/after so remove aStyleNode if existing
|
||||
|
||||
if (aStyleNode)
|
||||
{
|
||||
aView._DOMElement.removeChild(aStyleNode);
|
||||
aStyleNode = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return actualised values
|
||||
|
||||
@deref(aPreviousStateRef) = aPreviousState;
|
||||
|
||||
return aStyleNode;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)_shouldBeResized
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
|
||||
CPImageCSSBeforeDictionaryKey = @"CPImageCSSBeforeDictionaryKey",
|
||||
CPImageCSSAfterDictionaryKey = @"CPImageCSSAfterDictionaryKey";
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPImage (CPCoding)
|
||||
|
||||
/*!
|
||||
@@ -518,7 +748,10 @@ function CPAppKitImage(aFilename, aSize)
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
if ([aCoder containsValueForKey:CPImageCSSDictionaryKey])
|
||||
return [self initWithCSSDictionary:[aCoder decodeObjectForKey:CPImageCSSDictionaryKey] beforeDictionary:[aCoder decodeObjectForKey:CPImageCSSBeforeDictionaryKey] afterDictionary:[aCoder decodeObjectForKey:CPImageCSSAfterDictionaryKey] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
else
|
||||
return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -529,10 +762,180 @@ function CPAppKitImage(aFilename, aSize)
|
||||
{
|
||||
[aCoder encodeObject:_filename forKey:@"CPFilename"];
|
||||
[aCoder encodeSize:_size forKey:@"CPSize"];
|
||||
|
||||
// CSS Styling
|
||||
if ([self isCSSBased])
|
||||
{
|
||||
[aCoder encodeObject:_cssDictionary forKey:CPImageCSSDictionaryKey];
|
||||
[aCoder encodeObject:_cssBeforeDictionary forKey:CPImageCSSBeforeDictionaryKey];
|
||||
[aCoder encodeObject:_cssAfterDictionary forKey:CPImageCSSAfterDictionaryKey];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMaterialIconImage : CPImage
|
||||
{
|
||||
CPMutableDictionary _cachedColorVersions;
|
||||
CPColor _cachedInvertedColor;
|
||||
}
|
||||
|
||||
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size
|
||||
{
|
||||
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size];
|
||||
}
|
||||
|
||||
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color
|
||||
{
|
||||
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size color:color];
|
||||
}
|
||||
|
||||
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color additionalCSSDictionary:(CPDictionary)additionalCSSDictionary
|
||||
{
|
||||
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size color:color additionalCSSDictionary:additionalCSSDictionary];
|
||||
}
|
||||
|
||||
- (CPDictionary)_baseMaterialIconCSSDictionaryForIconName:(CPString)iconName size:(CGSize)size
|
||||
{
|
||||
return @{
|
||||
@"width": size.width + @"px",
|
||||
@"height": size.height + @"px",
|
||||
@"top": @"0px",
|
||||
@"left": @"0px",
|
||||
@"content": @"'" + iconName + @"'",
|
||||
|
||||
@"position": @"absolute",
|
||||
@"z-index": @"300",
|
||||
|
||||
@"font-family": @"'Material Icons'",
|
||||
@"font-weight": @"normal",
|
||||
@"font-style": @"normal",
|
||||
@"font-size": MIN(size.width, size.height) + @"px",
|
||||
@"display": @"inline-block",
|
||||
@"line-height": @"1",
|
||||
@"text-transform": @"none",
|
||||
@"letter-spacing": @"normal",
|
||||
@"word-wrap": @"normal",
|
||||
@"white-space": @"nowrap",
|
||||
@"direction": @"ltr",
|
||||
@"-webkit-font-smoothing": @"antialiased",
|
||||
@"text-rendering": @"optimizeLegibility",
|
||||
@"-moz-osx-font-smoothing": @"grayscale",
|
||||
@"font-feature-settings": @"'liga'"
|
||||
};
|
||||
}
|
||||
|
||||
- (_CPMaterialIconImage)initWithIconName:(CPString)iconName size:(CGSize)size
|
||||
{
|
||||
return [super initWithCSSDictionary:@{}
|
||||
beforeDictionary:@{}
|
||||
afterDictionary:[self _baseMaterialIconCSSDictionaryForIconName:iconName size:size]
|
||||
size:size];
|
||||
}
|
||||
|
||||
- (_CPMaterialIconImage)initWithIconName:(CPString)iconName size:(CGSize)size color:(CPColor)color
|
||||
{
|
||||
var materialIconCSSDictionary = [self _baseMaterialIconCSSDictionaryForIconName:iconName size:size];
|
||||
|
||||
[materialIconCSSDictionary setObject:[color cssString] forKey:@"color"];
|
||||
|
||||
return [super initWithCSSDictionary:@{}
|
||||
beforeDictionary:@{}
|
||||
afterDictionary:materialIconCSSDictionary
|
||||
size:size];
|
||||
}
|
||||
|
||||
- (void)addRotationEffectWithAngle:(float)angle
|
||||
{
|
||||
[self addCSSDictionary:@{
|
||||
@"transform": @"rotate("+angle+"deg)",
|
||||
@"transition": @"transform 0.35s ease"
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)addCSSDictionary:(CPDictionary)additionalCSSDictionary
|
||||
{
|
||||
[_cssAfterDictionary addEntriesFromDictionary:additionalCSSDictionary];
|
||||
}
|
||||
|
||||
- (void)setSize:(CGSize)aSize
|
||||
{
|
||||
[self _setDisplaySize:aSize];
|
||||
[super setSize:aSize];
|
||||
}
|
||||
|
||||
- (void)_setDisplaySize:(CGSize)aSize
|
||||
{
|
||||
if (CGSizeEqualToSize(_displaySize, aSize))
|
||||
return;
|
||||
|
||||
_displaySize = CGSizeMakeCopy(aSize);
|
||||
|
||||
[_cssAfterDictionary setObject:(aSize.width + @"px") forKey:@"width"];
|
||||
[_cssAfterDictionary setObject:(aSize.height + @"px") forKey:@"height"];
|
||||
[_cssAfterDictionary setObject:(MIN(aSize.width, aSize.height) + @"px") forKey:@"font-size"];
|
||||
}
|
||||
|
||||
- (BOOL)_shouldBeResized
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isMaterialIconImage
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (_CPMaterialIconImage)invertedImage
|
||||
{
|
||||
if (!_cachedInvertedColor)
|
||||
{
|
||||
var sourceCSSColor = [_cssAfterDictionary objectForKey:@"color"] || @"rgba(0,0,0,1)",
|
||||
sourceColor = [CPColor colorWithCSSString:sourceCSSColor];
|
||||
|
||||
_cachedInvertedColor = [CPColor colorWithRed:(1-[sourceColor redComponent])
|
||||
green:(1-[sourceColor greenComponent])
|
||||
blue:(1-[sourceColor blueComponent])
|
||||
alpha:[sourceColor alphaComponent]];
|
||||
}
|
||||
|
||||
return [self imageVersionWithColor:_cachedInvertedColor];
|
||||
}
|
||||
|
||||
- (_CPMaterialIconImage)imageVersionWithColor:(CPColor)aColor
|
||||
{
|
||||
// We can't just set the color in the cssAfterDictionary as this would not be noticed as a new image,
|
||||
// so -setImage won't do anything, so no visual refresh won't occur.
|
||||
// The trick here is to keep in cache a clone of this image for each needed color.
|
||||
var colorCSSString = [aColor cssString];
|
||||
|
||||
if (!_cachedColorVersions)
|
||||
_cachedColorVersions = @{};
|
||||
|
||||
var cachedColorVersion = [_cachedColorVersions objectForKey:colorCSSString];
|
||||
|
||||
if (!cachedColorVersion)
|
||||
{
|
||||
cachedColorVersion = [self duplicate];
|
||||
[cachedColorVersion _setCSSColor:colorCSSString];
|
||||
|
||||
[_cachedColorVersions setObject:cachedColorVersion forKey:colorCSSString];
|
||||
}
|
||||
|
||||
return cachedColorVersion;
|
||||
}
|
||||
|
||||
- (void)_setCSSColor:(CPString)aCSSColor
|
||||
{
|
||||
[_cssAfterDictionary setObject:aCSSColor forKey:@"color"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPThreePartImage : CPObject
|
||||
{
|
||||
CPArray _imageSlices;
|
||||
@@ -673,3 +1076,14 @@ var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPImage (Duplication)
|
||||
|
||||
- (CPImage)duplicate
|
||||
{
|
||||
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:self]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+47
-6
@@ -29,6 +29,8 @@
|
||||
@global CPImagesPboardType
|
||||
@global appkit_tag_dom_elements
|
||||
|
||||
@global document
|
||||
|
||||
@typedef CPImageAlignment
|
||||
CPImageAlignCenter = 0;
|
||||
CPImageAlignTop = 1;
|
||||
@@ -98,10 +100,32 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
- (void)_createDOMImageElement
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (_DOMImageElement)
|
||||
return;
|
||||
var image = [self objectValue],
|
||||
isCSSBasedImage = [image isCSSBased],
|
||||
isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG");
|
||||
|
||||
_DOMImageElement = document.createElement("img");
|
||||
// First, check if we need to destroy a current DOM image element. This is the case if :
|
||||
// - we have one but not the right one (that is a DIV but needing an IMG, and vice versa)
|
||||
|
||||
if (_DOMImageElement)
|
||||
{
|
||||
if ((isIMGImageElement && isCSSBasedImage) || (!isIMGImageElement && !isCSSBasedImage))
|
||||
{
|
||||
// OK, destroy it
|
||||
|
||||
_DOMElement.removeChild(_DOMImageElement);
|
||||
|
||||
_DOMImageElement = nil;
|
||||
|
||||
// CSS styling cleaning
|
||||
_cssStylePreviousState = @[];
|
||||
_cssStyleNode = nil;
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
_DOMImageElement = document.createElement(isCSSBasedImage ? "div" : "img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
@@ -150,10 +174,15 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
var newImage = [self objectValue];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (!_DOMImageElement)
|
||||
[self _createDOMImageElement];
|
||||
[self _createDOMImageElement];
|
||||
|
||||
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
|
||||
if ([newImage isCSSBased])
|
||||
_cssStyleNode = [newImage applyCSSImageForView:self
|
||||
onDOMElement:_DOMImageElement
|
||||
styleNode:_cssStyleNode
|
||||
previousState:@ref(_cssStylePreviousState)];
|
||||
else
|
||||
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
|
||||
#endif
|
||||
|
||||
var size = [newImage size];
|
||||
@@ -417,6 +446,18 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if ([image isCSSBased] && [image _shouldBeResized])
|
||||
{
|
||||
[image _setDisplaySize:CGSizeMake(ROUND(width), ROUND(height))];
|
||||
|
||||
_cssStyleNode = [image applyCSSImageForView:self
|
||||
onDOMElement:_DOMImageElement
|
||||
styleNode:_cssStyleNode
|
||||
previousState:@ref(_cssStylePreviousState)];
|
||||
}
|
||||
#endif
|
||||
|
||||
_imageRect = CGRectMake(x, y, width, height);
|
||||
|
||||
if (_hasShadow)
|
||||
|
||||
@@ -293,7 +293,7 @@ var CPBindingOperationAnd = 0,
|
||||
// If the value is nil AND the source doesn't respond to setPlaceholderString: then
|
||||
// we set the value to the placeholder. Otherwise, we do not want to short cut the process
|
||||
// of setting the placeholder that is based on the fact that the value is nil.
|
||||
if ((aValue === undefined || aValue === nil || aValue === [CPNull null])
|
||||
if ((aValue == nil || aValue === [CPNull null])
|
||||
&& ![_source respondsToSelector:@selector(setPlaceholderString:)])
|
||||
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
|
||||
|
||||
@@ -653,7 +653,7 @@ var CPBindingOperationAnd = 0,
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
||||
value = [object valueForKeyPath:keyPath];
|
||||
|
||||
if (value === nil || value === undefined)
|
||||
if (value == nil)
|
||||
{
|
||||
[_source setEnabled:NO];
|
||||
return;
|
||||
@@ -796,7 +796,7 @@ var CPBindingOperationAnd = 0,
|
||||
else
|
||||
value = [theBinding transformValue:value withOptions:options];
|
||||
|
||||
if (value === nil || value === undefined)
|
||||
if (value == nil)
|
||||
value = @"";
|
||||
|
||||
result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]);
|
||||
@@ -857,7 +857,7 @@ var CPBindingOperationAnd = 0,
|
||||
- (id)initWithName:(CPString)aName
|
||||
{
|
||||
if (self = [super init])
|
||||
_name = aName
|
||||
_name = aName;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -918,6 +918,7 @@ CPTextColorBinding = @"textColor";
|
||||
CPTitleBinding = @"title";
|
||||
CPToolTipBinding = @"toolTip";
|
||||
CPValueBinding = @"value";
|
||||
CPAttributedStringBinding = @"attributedString";
|
||||
CPValueURLBinding = @"valueURL";
|
||||
CPValuePathBinding = @"valuePath";
|
||||
CPDataBinding = @"data";
|
||||
|
||||
@@ -22,15 +22,10 @@
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPWindow_Constants.j"
|
||||
@import "CPSlider.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
@typedef CPTickMarkPosition
|
||||
CPTickMarkBelow = 0;
|
||||
CPTickMarkAbove = 1;
|
||||
CPTickMarkLeft = CPTickMarkAbove;
|
||||
CPTickMarkRight = CPTickMarkBelow;
|
||||
|
||||
@typedef CPLevelIndicatorStyle
|
||||
CPRelevancyLevelIndicatorStyle = 0;
|
||||
CPContinuousCapacityLevelIndicatorStyle = 1;
|
||||
@@ -292,7 +287,7 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setWarningValue:(double)warningValue;
|
||||
- (void)setWarningValue:(double)warningValue
|
||||
{
|
||||
if (_warningValue === warningValue)
|
||||
return;
|
||||
@@ -301,7 +296,7 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setCriticalValue:(double)criticalValue;
|
||||
- (void)setCriticalValue:(double)criticalValue
|
||||
{
|
||||
if (_criticalValue === criticalValue)
|
||||
return;
|
||||
|
||||
+33
-2
@@ -164,7 +164,7 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
+ (void)_setOrRemoveMenuBarAttribute:(id)aValue forKey:(id)aKey
|
||||
{
|
||||
if (aValue === nil)
|
||||
if (aValue == nil)
|
||||
[_CPMenuBarAttributes removeObjectForKey:aKey];
|
||||
else
|
||||
[_CPMenuBarAttributes setObject:aValue forKey:aKey];
|
||||
@@ -1131,7 +1131,7 @@ var _CPMenuBarVisible = NO,
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_menuWithName:(CPString)aName
|
||||
- (CPMenu)_menuWithName:(CPString)aName
|
||||
{
|
||||
if (aName === _name)
|
||||
return self;
|
||||
@@ -1293,6 +1293,37 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPMenu (CSSTheming)
|
||||
|
||||
+ (void)setNamedValue:(CPString)aName forKey:(CPString)aKey inAttributes:(CPDictionary)aDictionary forTheme:(CPTheme)aTheme
|
||||
{
|
||||
var value = [aTheme valueForAttributeWithName:aName forClass:_CPMenuView];
|
||||
|
||||
if (value)
|
||||
[aDictionary setObject:value forKey:aKey];
|
||||
else
|
||||
[aDictionary removeObjectForKey:aKey];
|
||||
}
|
||||
|
||||
+ (void)updateMenuBarAttributesWithTheme:(CPTheme)aTheme
|
||||
{
|
||||
var newAttributes = @{};
|
||||
|
||||
[CPMenu setNamedValue:@"menu-bar-text-color" forKey:@"CPMenuBarTextColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
[CPMenu setNamedValue:@"menu-bar-title-color" forKey:@"CPMenuBarTitleColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
[CPMenu setNamedValue:@"menu-bar-text-shadow-color" forKey:@"CPMenuBarTextShadowColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
[CPMenu setNamedValue:@"menu-bar-title-shadow-color" forKey:@"CPMenuBarTitleShadowColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
[CPMenu setNamedValue:@"menu-bar-highlight-color" forKey:@"CPMenuBarHighlightColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
[CPMenu setNamedValue:@"menu-bar-highlight-text-color" forKey:@"CPMenuBarHighlightTextColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
[CPMenu setNamedValue:@"menu-bar-highlight-text-shadow-color" forKey:@"CPMenuBarHighlightTextShadowColor" inAttributes:newAttributes forTheme:aTheme];
|
||||
|
||||
[CPMenu setMenuBarAttributes:newAttributes];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "_CPMenuBarWindow.j"
|
||||
@import "_CPMenuWindow.j"
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
|
||||
@class _CPMenuView
|
||||
@class CPMenu
|
||||
@class CPMenuItem
|
||||
|
||||
@global CPMenuDidAddItemNotification
|
||||
@global CPMenuDidChangeItemNotification
|
||||
@global CPMenuDidRemoveItemNotification
|
||||
|
||||
@global document
|
||||
|
||||
@implementation _CPMenuBarWindow : CPPanel
|
||||
{
|
||||
CPView _highlightView;
|
||||
@@ -129,10 +132,12 @@
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
var targetView = [[CPTheme defaultTheme] valueForAttributeWithName:@"css-based" forClass:CPView] ? [[self contentView] superview] : [self contentView];
|
||||
|
||||
if (!aColor)
|
||||
[[self contentView] setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-color" forClass:_CPMenuView]];
|
||||
[targetView setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-color" forClass:_CPMenuView]];
|
||||
else
|
||||
[[self contentView] setBackgroundColor:aColor];
|
||||
[targetView setBackgroundColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
@@ -277,6 +282,16 @@
|
||||
[menuItemView setTextColor:_textColor];
|
||||
[menuItemView setHidden:[item isHidden]];
|
||||
|
||||
// If first menu item has tag -1 and if there is a special theme value menu-bar-window-first-item-font,
|
||||
// set the corresponding font. This is used to set bold on the first item of the menubar (à la Cocoa)
|
||||
if ((index == 0) && ([item tag] == -1))
|
||||
{
|
||||
var firstItemFont = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-first-item-font" forClass:_CPMenuView];
|
||||
|
||||
if (firstItemFont)
|
||||
[item setFont:firstItemFont];
|
||||
}
|
||||
|
||||
[menuItemView synchronizeWithMenuItem];
|
||||
|
||||
[contentView addSubview:menuItemView];
|
||||
@@ -350,7 +365,7 @@
|
||||
|
||||
- (CPFont)font
|
||||
{
|
||||
[CPFont systemFontOfSize:[CPFont systemFontSize]];
|
||||
return [CPFont systemFontOfSize:[CPFont systemFontSize]];
|
||||
}
|
||||
|
||||
- (void)tile
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
@class CPWindow
|
||||
@class _CPMenuWindow
|
||||
@class _CPMenuView
|
||||
@class CPMenuItem
|
||||
|
||||
|
||||
@global CPApp
|
||||
@@ -223,7 +225,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
_lastGlobalLocation = globalLocation;
|
||||
|
||||
// If the item isn't enabled its as if we clicked on nothing.
|
||||
if (![activeItem isEnabled] || [activeItem _isMenuBarButton])
|
||||
if ([activeItem _isMenuBarButton])
|
||||
{
|
||||
activeItemIndex = CPNotFound;
|
||||
activeItem = nil;
|
||||
@@ -352,7 +354,23 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
if ([activeMenuContainer isMenuBar])
|
||||
newMenuOrigin = CGPointMake(CGRectGetMinX(activeItemRect), CGRectGetMaxY(activeItemRect));
|
||||
else
|
||||
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect), CGRectGetMinY(activeItemRect));
|
||||
{
|
||||
// New theme attributes to have more precise submenus positioning
|
||||
|
||||
var defaultTheme = [CPTheme defaultTheme],
|
||||
themeDeltaX = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-delta-x" forClass:_CPMenuView],
|
||||
themeDeltaY = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-delta-y" forClass:_CPMenuView],
|
||||
themeFirstDeltaY = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-first-level-delta-y" forClass:_CPMenuView],
|
||||
activeMenuIndex = [_menuContainerStack indexOfObject:activeMenuContainer],
|
||||
|
||||
deltaX = themeDeltaX ? themeDeltaX : 0,
|
||||
deltaY = themeDeltaY ? themeDeltaY : 0;
|
||||
|
||||
if (themeFirstDeltaY && (activeMenuIndex == 1) && [_menuContainerStack[0] isMenuBar])
|
||||
deltaY += themeFirstDeltaY;
|
||||
|
||||
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect)+deltaX, CGRectGetMinY(activeItemRect)+deltaY);
|
||||
}
|
||||
|
||||
newMenuOrigin = [activeMenuContainer convertBaseToGlobal:newMenuOrigin];
|
||||
|
||||
@@ -553,7 +571,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
var iter = [selectorNames objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
{
|
||||
var aSelector = CPSelectorFromString(obj);
|
||||
|
||||
@@ -580,6 +598,8 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu];
|
||||
_lastGlobalLocation = nil;
|
||||
}
|
||||
|
||||
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
|
||||
}
|
||||
|
||||
- (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu
|
||||
@@ -587,7 +607,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
var iter = [[menu itemArray] objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
{
|
||||
if ([obj isHidden] || ![obj isEnabled])
|
||||
continue;
|
||||
@@ -725,39 +745,32 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
|
||||
- (void)moveDown:(CPMenu)menu
|
||||
{
|
||||
var index = menu._highlightedIndex + 1;
|
||||
var index = menu._highlightedIndex + 1,
|
||||
item;
|
||||
|
||||
// Search for the next enabled item
|
||||
while ((index < [menu numberOfItems]) && (item = [menu itemAtIndex:index]) && ([item isSeparatorItem] || [item isHidden] || ![item isEnabled]))
|
||||
index++;
|
||||
|
||||
if (index < [menu numberOfItems])
|
||||
{
|
||||
[menu _highlightItemAtIndex:index];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveDown:menu];
|
||||
}
|
||||
else if (menu == [CPApp mainMenu])
|
||||
[menu _highlightItemAtIndex:0];
|
||||
}
|
||||
|
||||
- (void)moveUp:(CPMenu)menu
|
||||
{
|
||||
var index = menu._highlightedIndex - 1;
|
||||
var index = menu._highlightedIndex - 1,
|
||||
item;
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
if (index != CPNotFound || menu == [CPApp mainMenu])
|
||||
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
// Search for the previous enabled item
|
||||
while ((index >= 0) && (item = [menu itemAtIndex:index]) && ([item isSeparatorItem] || [item isHidden] || ![item isEnabled]))
|
||||
index--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[menu _highlightItemAtIndex:index];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveUp:menu];
|
||||
if (index >= 0)
|
||||
[menu _highlightItemAtIndex:index];
|
||||
else if (menu == [CPApp mainMenu])
|
||||
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
}
|
||||
|
||||
- (void)insertNewline:(CPMenu)menu
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
@import "_CPMenuManager.j"
|
||||
|
||||
@class _CPMenuView
|
||||
@class CPMenuItem
|
||||
|
||||
var _CPMenuWindowPool = [],
|
||||
_CPMenuWindowPoolCapacity = 5,
|
||||
@@ -419,6 +420,21 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMenuWindow (CSSTheming)
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[[self contentView] _setThemeIncludingDescendants:aTheme];
|
||||
[_menuView _setThemeIncludingDescendants:aTheme];
|
||||
[_menuView tile];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@@ -449,6 +465,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
@"menu-bar-window-background-color": [CPNull null],
|
||||
@"menu-bar-window-background-selected-color": [CPNull null],
|
||||
@"menu-bar-window-font": [CPNull null],
|
||||
@"menu-bar-window-first-item-font": [CPNull null],
|
||||
@"menu-bar-window-height": 30.0,
|
||||
@"menu-bar-window-margin": 10.0,
|
||||
@"menu-bar-window-left-margin": 10.0,
|
||||
@@ -466,6 +483,9 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
@"menu-general-icon-new": [CPNull null],
|
||||
@"menu-general-icon-save": [CPNull null],
|
||||
@"menu-general-icon-open": [CPNull null],
|
||||
@"menu-window-submenu-delta-x": 0.0,
|
||||
@"menu-window-submenu-delta-y": 0.0,
|
||||
@"menu-window-submenu-first-level-delta-y": 0.0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -552,6 +572,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
[view setFrameOrigin:CGPointMake(0.0, y)];
|
||||
|
||||
[view _setThemeIncludingDescendants:[CPTheme defaultTheme]];
|
||||
[self addSubview:view];
|
||||
|
||||
var size = [view minSize],
|
||||
@@ -582,3 +603,18 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMenuView (CSSTheming)
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[self setTheme:aTheme];
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
|
||||
|
||||
for (var i = 0, items = [[self menu] itemArray], count = [items count]; i < count; i++)
|
||||
[[items[i] _menuItemView] _setThemeIncludingDescendants:aTheme];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -505,7 +505,7 @@ CPOffState
|
||||
if (_submenu)
|
||||
{
|
||||
[_submenu setSupermenu:_menu];
|
||||
[_submenu setTitle:[self title]]
|
||||
[_submenu setTitle:[self title]];
|
||||
|
||||
[self setTarget:_menu];
|
||||
[self setAction:@selector(submenuAction:)];
|
||||
@@ -843,7 +843,7 @@ CPControlKeyMask
|
||||
[item setTarget:_target];
|
||||
[item setAction:_action];
|
||||
[item setEnabled:_isEnabled];
|
||||
[item setHidden:_isHidden]
|
||||
[item setHidden:_isHidden];
|
||||
[item setTag:_tag];
|
||||
[item setState:_state];
|
||||
[item setImage:_image];
|
||||
@@ -897,6 +897,20 @@ CPControlKeyMask
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPMenuItem (CSSTheming)
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[_view _setThemeIncludingDescendants:aTheme];
|
||||
[_menuItemView _setThemeIncludingDescendants:aTheme];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
|
||||
|
||||
CPMenuItemTitleKey = @"CPMenuItemTitleKey",
|
||||
@@ -911,6 +925,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
|
||||
|
||||
CPMenuItemImageKey = @"CPMenuItemImageKey",
|
||||
CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey",
|
||||
CPMenuItemOnStateImageKey = @"CPMenuItemOnStateImageKey",
|
||||
CPMenuItemOffStateImageKey = @"CPMenuItemOffStateImageKey",
|
||||
CPMenuItemMixedStateImageKey = @"CPMenuItemMixedStateImageKey",
|
||||
|
||||
CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey",
|
||||
CPMenuItemMenuKey = @"CPMenuItemMenuKey",
|
||||
@@ -955,9 +972,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
|
||||
|
||||
_image = [aCoder decodeObjectForKey:CPMenuItemImageKey];
|
||||
_alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey];
|
||||
// CPImage _onStateImage;
|
||||
// CPImage _offStateImage;
|
||||
// CPImage _mixedStateImage;
|
||||
_onStateImage = [aCoder decodeObjectForKey:CPMenuItemOnStateImageKey];
|
||||
_offStateImage = [aCoder decodeObjectForKey:CPMenuItemOffStateImageKey];
|
||||
_mixedStateImage = [aCoder decodeObjectForKey:CPMenuItemMixedStateImageKey];
|
||||
|
||||
// This order matters because setSubmenu: needs _menu to be around.
|
||||
_menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey];
|
||||
@@ -1003,6 +1020,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
|
||||
|
||||
ENCODE_IFNOT(CPMenuItemImageKey, _image, nil);
|
||||
ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil);
|
||||
ENCODE_IFNOT(CPMenuItemOnStateImageKey, _onStateImage, nil);
|
||||
ENCODE_IFNOT(CPMenuItemOffStateImageKey, _offStateImage, nil);
|
||||
ENCODE_IFNOT(CPMenuItemMixedStateImageKey, _mixedStateImage, nil);
|
||||
|
||||
ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
|
||||
ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
|
||||
+ (id)view
|
||||
{
|
||||
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 10.0)];
|
||||
var themedHeight = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-view-height" forClass:_CPMenuItemStandardView];
|
||||
|
||||
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, (themedHeight ? themedHeight : 10.0))];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -47,12 +49,25 @@
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
bounds = [self bounds],
|
||||
height = CGRectGetMaxY(bounds),
|
||||
themedHeight = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-height" forClass:_CPMenuItemStandardView],
|
||||
lineHeight = themedHeight ? themedHeight : 1.0;
|
||||
|
||||
CGContextBeginPath(context);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
CGContextSetLineWidth(context, lineHeight);
|
||||
|
||||
if (!!((height - lineHeight) % 2))
|
||||
{
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)));
|
||||
}
|
||||
|
||||
CGContextSetStrokeColor(context, [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-color" forClass:_CPMenuItemStandardView]);
|
||||
CGContextStrokePath(context);
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
_CPImageAndTextView _imageAndTextView;
|
||||
_CPImageAndTextView _keyEquivalentView;
|
||||
CPView _submenuIndicatorView;
|
||||
|
||||
BOOL _hasSubmenuIndicatorImage;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
@@ -55,6 +57,7 @@
|
||||
@"menu-item-selection-color": [CPNull null],
|
||||
@"menu-item-text-shadow-color": [CPNull null],
|
||||
@"menu-item-text-color": [CPNull null],
|
||||
@"menu-item-disabled-text-color": [CPColor lightGrayColor],
|
||||
@"menu-item-default-off-state-image": [CPNull null],
|
||||
@"menu-item-default-off-state-highlighted-image": [CPNull null],
|
||||
@"menu-item-default-on-state-image": [CPNull null],
|
||||
@@ -62,12 +65,17 @@
|
||||
@"menu-item-default-mixed-state-image": [CPNull null],
|
||||
@"menu-item-default-mixed-state-highlighted-image": [CPNull null],
|
||||
@"menu-item-separator-color": [CPNull null],
|
||||
@"menu-item-separator-height": 1.0,
|
||||
@"menu-item-separator-view-height": 10.0,
|
||||
@"left-margin": 3.0,
|
||||
@"right-margin": 17.0,
|
||||
@"state-column-width": 14.0,
|
||||
@"indentation-width": 17.0,
|
||||
@"vertical-margin": 4.0,
|
||||
@"vertical-offset": 0.0,
|
||||
@"right-columns-margin": 30.0,
|
||||
@"submenu-indicator-image": [CPNull null],
|
||||
@"submenu-indicator-highlighted-image": [CPNull null]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,6 +98,7 @@
|
||||
_stateView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
|
||||
|
||||
[_stateView setImageScaling:CPImageScaleNone];
|
||||
[_stateView setImageAlignment:CPImageAlignCenter];
|
||||
|
||||
[self addSubview:_stateView];
|
||||
|
||||
@@ -108,9 +117,24 @@
|
||||
|
||||
[self addSubview:_keyEquivalentView];
|
||||
|
||||
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)];
|
||||
// Do we have a submenu indicator image specified in the theme ?
|
||||
_hasSubmenuIndicatorImage = !![self valueForThemeAttribute:@"submenu-indicator-image"];
|
||||
|
||||
if (_hasSubmenuIndicatorImage)
|
||||
{
|
||||
// Yes, then use an imageView
|
||||
_submenuIndicatorView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_submenuIndicatorView setImageAlignment:CPImageAlignCenter];
|
||||
}
|
||||
else
|
||||
{
|
||||
// No, then use self drawing _CPMenuItemSubmenuIndicatorView
|
||||
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)];
|
||||
|
||||
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
|
||||
}
|
||||
|
||||
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
|
||||
[_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin];
|
||||
|
||||
[self addSubview:_submenuIndicatorView];
|
||||
@@ -124,7 +148,7 @@
|
||||
- (CPColor)textColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor lightGrayColor];
|
||||
return [self valueForThemeAttribute:@"menu-item-disabled-text-color"];
|
||||
|
||||
if (_highlighted)
|
||||
return [CPColor whiteColor];
|
||||
@@ -148,11 +172,24 @@
|
||||
_font = aFont;
|
||||
}
|
||||
|
||||
- (CPFont)font
|
||||
{
|
||||
// Menu item font is forced local font or _menuItem font or system font
|
||||
return _font || [_menuItem font] || [CPFont systemFontOfSize:CPFontCurrentSystemSize];
|
||||
}
|
||||
|
||||
// FIXME: update is called 2 times at each display. Find why and fix.
|
||||
- (void)update
|
||||
{
|
||||
var x = [self valueForThemeAttribute:@"left-margin"] + [_menuItem indentationLevel] * [self valueForThemeAttribute:@"indentation-width"],
|
||||
height = 0.0,
|
||||
hasStateColumn = [[_menuItem menu] showsStateColumn];
|
||||
hasStateColumn = [[_menuItem menu] showsStateColumn],
|
||||
myFont = [self font],
|
||||
|
||||
// When possible, use specific vertical margin/offset value based on font size (which could have been set by control size)
|
||||
correspondingControlSize = [myFont controlSizeCorrespondingToFontSize],
|
||||
verticalMargin = [self valueForThemeAttribute:@"vertical-margin" inState:CPControlSizeThemeStates[correspondingControlSize]],
|
||||
verticalOffset = [self valueForThemeAttribute:@"vertical-offset" inState:CPControlSizeThemeStates[correspondingControlSize]];
|
||||
|
||||
if (hasStateColumn)
|
||||
{
|
||||
@@ -162,27 +199,32 @@
|
||||
switch ([_menuItem state])
|
||||
{
|
||||
case CPOnState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image"]];
|
||||
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image"]];
|
||||
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPMixedState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image"]];
|
||||
[_stateView setImage:[_menuItem mixedStateImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var stateViewFrameOrigin = [_stateView frameOrigin];
|
||||
|
||||
stateViewFrameOrigin.x = x;
|
||||
[_stateView setFrameOrigin:stateViewFrameOrigin];
|
||||
|
||||
x += [self valueForThemeAttribute:@"state-column-width"];
|
||||
}
|
||||
else
|
||||
[_stateView setHidden:YES];
|
||||
|
||||
[_imageAndTextView setFont:[_menuItem font] || _font];
|
||||
[_imageAndTextView setFont:myFont];
|
||||
[_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setText:[_menuItem title]];
|
||||
@@ -195,7 +237,7 @@
|
||||
|
||||
imageAndTextViewFrame.origin.x = x;
|
||||
x += CGRectGetWidth(imageAndTextViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame));
|
||||
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame)); // FIXME: here, height = 0 -> MAX useless
|
||||
|
||||
var hasKeyEquivalent = !![_menuItem keyEquivalent],
|
||||
hasSubmenu = [_menuItem hasSubmenu];
|
||||
@@ -205,14 +247,14 @@
|
||||
|
||||
if (hasKeyEquivalent)
|
||||
{
|
||||
[_keyEquivalentView setFont:[_menuItem font] || _font];
|
||||
[_keyEquivalentView setFont:myFont];
|
||||
[_keyEquivalentView setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
[_keyEquivalentView setImage:[_menuItem image]];
|
||||
[_keyEquivalentView setText:[_menuItem keyEquivalentStringRepresentation]];
|
||||
[_keyEquivalentView setTextColor:[self textColor]];
|
||||
[_keyEquivalentView setTextShadowColor:[self textShadowColor]];
|
||||
[_keyEquivalentView setTextShadowOffset:CGSizeMake(0, 1)];
|
||||
[_keyEquivalentView setFrameOrigin:CGPointMake(x, [self valueForThemeAttribute:@"vertical-margin"])];
|
||||
[_keyEquivalentView setFrameOrigin:CGPointMake(x, verticalMargin)];
|
||||
[_keyEquivalentView sizeToFit];
|
||||
|
||||
var keyEquivalentViewFrame = [_keyEquivalentView frame];
|
||||
@@ -229,6 +271,14 @@
|
||||
|
||||
if (hasSubmenu)
|
||||
{
|
||||
if (_hasSubmenuIndicatorImage)
|
||||
{
|
||||
var submenuIndicatorImage = [self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]];
|
||||
|
||||
[_submenuIndicatorView setImage:submenuIndicatorImage];
|
||||
[_submenuIndicatorView setFrameSize:[submenuIndicatorImage size]];
|
||||
}
|
||||
|
||||
[_submenuIndicatorView setHidden:NO];
|
||||
|
||||
var submenuViewFrame = [_submenuIndicatorView frame];
|
||||
@@ -241,9 +291,9 @@
|
||||
else
|
||||
[_submenuIndicatorView setHidden:YES];
|
||||
|
||||
height += 2.0 * [self valueForThemeAttribute:@"vertical-margin"];
|
||||
height += 2.0 * verticalMargin;
|
||||
|
||||
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0);
|
||||
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0) + verticalOffset;
|
||||
[_imageAndTextView setFrame:imageAndTextViewFrame];
|
||||
|
||||
if (hasStateColumn)
|
||||
@@ -251,7 +301,7 @@
|
||||
|
||||
if (hasKeyEquivalent)
|
||||
{
|
||||
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0);
|
||||
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0) + verticalOffset;
|
||||
[_keyEquivalentView setFrame:keyEquivalentViewFrame];
|
||||
}
|
||||
|
||||
@@ -276,6 +326,8 @@
|
||||
|
||||
_highlighted = shouldHighlight;
|
||||
|
||||
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize];
|
||||
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_keyEquivalentView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
@@ -285,13 +337,21 @@
|
||||
{
|
||||
[self setBackgroundColor:[self valueForThemeAttribute:@"menu-item-selection-color"]];
|
||||
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
|
||||
if (_hasSubmenuIndicatorImage)
|
||||
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
else
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:nil];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
|
||||
|
||||
if (_hasSubmenuIndicatorImage)
|
||||
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
else
|
||||
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
|
||||
}
|
||||
|
||||
if ([[_menuItem menu] showsStateColumn])
|
||||
@@ -301,15 +361,15 @@
|
||||
switch ([_menuItem state])
|
||||
{
|
||||
case CPOnState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image"]];
|
||||
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image"]];
|
||||
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPMixedState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image"]];
|
||||
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -321,15 +381,15 @@
|
||||
switch ([_menuItem state])
|
||||
{
|
||||
case CPOnState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image"]];
|
||||
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image"]];
|
||||
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPMixedState:
|
||||
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image"]];
|
||||
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -346,6 +406,22 @@
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMenuItemStandardView (CSSTheming)
|
||||
|
||||
#pragma mark Override
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[self setTheme:aTheme];
|
||||
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMenuItemSubmenuIndicatorView : CPView
|
||||
{
|
||||
CPColor _color;
|
||||
|
||||
@@ -252,6 +252,31 @@
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMenuItemView (CSSTheming)
|
||||
|
||||
#pragma mark Override
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[self setTheme:aTheme];
|
||||
[_view _setThemeIncludingDescendants:aTheme];
|
||||
|
||||
// Items must also perform this (without this, only the selected item does)
|
||||
|
||||
[_imageAndTextView _setThemeIncludingDescendants:aTheme];
|
||||
[_submenuView _setThemeIncludingDescendants:aTheme];
|
||||
[[_menuItem view] _setThemeIncludingDescendants:aTheme];
|
||||
|
||||
if ([_view respondsToSelector:@selector(update)])
|
||||
[_view update];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPMenuItemArrowView : CPView
|
||||
{
|
||||
CPColor _color;
|
||||
|
||||
@@ -192,6 +192,12 @@
|
||||
*/
|
||||
- (void)setEntityName:(CPString)newEntityName
|
||||
{
|
||||
if (!_managedProxy)
|
||||
{
|
||||
_managedProxy = [[_CPManagedProxy alloc] init];
|
||||
_isUsingManagedProxy = YES;
|
||||
}
|
||||
|
||||
[_managedProxy setEntityName:newEntityName];
|
||||
}
|
||||
|
||||
@@ -378,13 +384,35 @@
|
||||
*/
|
||||
- (void)_selectionDidChange
|
||||
{
|
||||
if (_selection === undefined || _selection === nil)
|
||||
if (_selection == nil)
|
||||
_selection = [[CPControllerSelectionProxy alloc] initWithController:self];
|
||||
|
||||
[_selection controllerDidChange];
|
||||
[self didChangeValueForKey:@"selection"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
These two private methods map CPTextField notifications to the CPEditorRegistration protocol
|
||||
This should be generalized in the future:
|
||||
|
||||
The CPEditorRegistrationProtocol can be implemented in all controls that support editing, not just CPTextField.
|
||||
In CPArrayController there are other cases than selection change when we need to review all editor pending changes. They should be covered, including the selection change, by the wider concept described by the methods commitEditing: (forces to end editing) and discardEditing: (pending changes are lost).
|
||||
*/
|
||||
- (void)_objectDidBeginEditing:(CPNotification)notification
|
||||
{
|
||||
[self objectDidBeginEditing:[notification object]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void) _objectDidEndEditing:(CPNotification)notification
|
||||
{
|
||||
[self objectDidEndEditing:[notification object]];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@return id - Returns the keys which are being observed.
|
||||
*/
|
||||
@@ -764,7 +792,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
}
|
||||
}
|
||||
|
||||
if (value === nil || value.isa && [value isEqual:[CPNull null]])
|
||||
if (value == nil || value.isa && [value isEqual:[CPNull null]])
|
||||
value = CPNullMarker;
|
||||
|
||||
return value;
|
||||
|
||||
@@ -1438,7 +1438,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
_shouldRetargetChildIndex = YES;
|
||||
|
||||
// set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
|
||||
var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
|
||||
var retargetedItemInfo = (_retargetedItem != nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
|
||||
|
||||
if (_retargedChildIndex === [retargetedItemInfo.children count])
|
||||
{
|
||||
@@ -1881,7 +1881,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPOutlineViewTableViewDataSource : CPObject
|
||||
@implementation _CPOutlineViewTableViewDataSource : CPObject <CPTableViewDataSource>
|
||||
{
|
||||
CPObject _outlineView;
|
||||
}
|
||||
@@ -1923,7 +1923,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
while (index !== CPNotFound)
|
||||
{
|
||||
[items addObject:[_outlineView itemAtRow:index]]
|
||||
[items addObject:[_outlineView itemAtRow:index]];
|
||||
index = [theIndexes indexGreaterThanIndex:index];
|
||||
}
|
||||
|
||||
@@ -1940,7 +1940,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
if (theDropOperation === CPTableViewDropAbove)
|
||||
{
|
||||
var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
|
||||
itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
|
||||
itemInfo = (parentItem != nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
|
||||
children = itemInfo.children;
|
||||
|
||||
childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
|
||||
@@ -1957,7 +1957,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
- (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
|
||||
{
|
||||
if (theDropOperation === CPTableViewDropAbove)
|
||||
return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset]
|
||||
return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset];
|
||||
|
||||
return [_outlineView itemAtRow:theRow];
|
||||
}
|
||||
@@ -2011,7 +2011,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPOutlineViewTableViewDelegate : CPObject
|
||||
@implementation _CPOutlineViewTableViewDelegate : CPObject <CPTableViewDelegate>
|
||||
{
|
||||
CPOutlineView _outlineView;
|
||||
}
|
||||
|
||||
+4
-69
@@ -44,6 +44,9 @@ CPStringPboardType = @"CPStringPboardType";
|
||||
CPURLPboardType = @"CPURLPboardType";
|
||||
CPImagesPboardType = @"CPImagesPboardType";
|
||||
CPVideosPboardType = @"CPVideosPboardType";
|
||||
CPRTFPboardType = @"CPRTFPboardType";
|
||||
_CPSmartPboardType = @"_CPSmartPboardType";
|
||||
_CPASPboardType = @"_CPASPboardType";
|
||||
|
||||
UTF8PboardType = @"public.utf8-plain-text";
|
||||
|
||||
@@ -51,8 +54,7 @@ UTF8PboardType = @"public.utf8-plain-text";
|
||||
CPImagePboardType = @"CPImagePboardType";
|
||||
|
||||
|
||||
var CPPasteboards = nil,
|
||||
supportsNativePasteboard = NO;
|
||||
var CPPasteboards = nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -68,8 +70,6 @@ var CPPasteboards = nil,
|
||||
|
||||
unsigned _changeCount;
|
||||
CPString _stateUID;
|
||||
|
||||
CPWebScriptObject _nativePasteboard;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -83,9 +83,6 @@ var CPPasteboards = nil,
|
||||
[self setVersion:1.0];
|
||||
|
||||
CPPasteboards = @{};
|
||||
|
||||
if (typeof window.cpPasteboardWithName !== "undefined")
|
||||
supportsNativePasteboard = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -128,12 +125,6 @@ var CPPasteboards = nil,
|
||||
_provided = @{};
|
||||
|
||||
_changeCount = 0;
|
||||
|
||||
if (supportsNativePasteboard)
|
||||
{
|
||||
_nativePasteboard = window.cpPasteboardWithName(aName);
|
||||
[self _synchronizePasteboard];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -163,15 +154,6 @@ var CPPasteboards = nil,
|
||||
[_owners setObject:anOwner forKey:type];
|
||||
}
|
||||
|
||||
if (_nativePasteboard)
|
||||
{
|
||||
var nativeTypes = [types copy];
|
||||
if ([types containsObject:CPStringPboardType])
|
||||
nativeTypes.push(UTF8PboardType);
|
||||
|
||||
_nativePasteboard.addTypes_(nativeTypes);
|
||||
}
|
||||
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
@@ -182,12 +164,6 @@ var CPPasteboards = nil,
|
||||
@return the pasteboard's change count
|
||||
*/
|
||||
- (unsigned)declareTypes:(CPArray)types owner:(id)anOwner
|
||||
{
|
||||
[self _declareTypes:types owner:anOwner updateNativePasteboard:YES];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (unsigned)_declareTypes:(CPArray)types owner:(id)anOwner updateNativePasteboard:(BOOL)shouldUpdate
|
||||
{
|
||||
[_types setArray:types];
|
||||
|
||||
@@ -201,16 +177,6 @@ var CPPasteboards = nil,
|
||||
[_owners setObject:anOwner forKey:_types[count]];
|
||||
}
|
||||
|
||||
if (_nativePasteboard && shouldUpdate)
|
||||
{
|
||||
var nativeTypes = [types copy];
|
||||
if ([types containsObject:CPStringPboardType])
|
||||
nativeTypes.push(UTF8PboardType);
|
||||
|
||||
_nativePasteboard.declareTypes_(nativeTypes);
|
||||
_changeCount = _nativePasteboard.changeCount();
|
||||
}
|
||||
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
@@ -273,7 +239,6 @@ var CPPasteboards = nil,
|
||||
*/
|
||||
- (CPArray)types
|
||||
{
|
||||
[self _synchronizePasteboard];
|
||||
return _types;
|
||||
}
|
||||
|
||||
@@ -312,36 +277,6 @@ var CPPasteboards = nil,
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_synchronizePasteboard
|
||||
{
|
||||
if (_nativePasteboard && _nativePasteboard.changeCount() > _changeCount)
|
||||
{
|
||||
var nativeTypes = [_nativePasteboard.types() copy];
|
||||
if ([nativeTypes containsObject:UTF8PboardType])
|
||||
nativeTypes.push(CPStringPboardType);
|
||||
|
||||
[self _declareTypes:nativeTypes owner:self updateNativePasteboard:NO];
|
||||
|
||||
_changeCount = _nativePasteboard.changeCount();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @ignore
|
||||
method provided for integration with native pasteboard
|
||||
*/
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if (aType === CPStringPboardType)
|
||||
{
|
||||
var string = _nativePasteboard.stringForType_(UTF8PboardType);
|
||||
|
||||
[self setString:string forType:CPStringPboardType];
|
||||
[self setString:string forType:UTF8PboardType];
|
||||
}
|
||||
else
|
||||
[self setString:_nativePasteboard.stringForType_(aType) forType:aType];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the property list for the specified data type
|
||||
@param aType the requested data type
|
||||
|
||||
+44
-7
@@ -45,6 +45,13 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
return "popup-button";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"menu-offset": CGSizeMake(0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingSelectedIndex
|
||||
{
|
||||
return [CPSet setWithObject:@"objectValue"];
|
||||
@@ -491,9 +498,17 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
[self addItemWithTitle:aTitle];
|
||||
|
||||
index = [self numberOfItems] - 1;
|
||||
// this ist to match cocoa where setting an empty string does not add it but simply clears the title
|
||||
// and sets objectValue to -1
|
||||
if (aTitle === '')
|
||||
[self selectItemAtIndex:-1];
|
||||
else
|
||||
{
|
||||
[self addItemWithTitle:aTitle];
|
||||
|
||||
index = [self numberOfItems] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
[self selectItemAtIndex:index];
|
||||
@@ -677,14 +692,16 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
if ([self pullsDown])
|
||||
{
|
||||
var positionedItem = nil,
|
||||
location = CGPointMake(0.0, CGRectGetMaxY(bounds) - 1);
|
||||
menuOffset = [self currentValueForThemeAttribute:@"menu-offset"],
|
||||
location = CGPointMake(menuOffset.width, CGRectGetMaxY(bounds) + menuOffset.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
var contentRect = [self contentRectForBounds:bounds],
|
||||
positionedItem = [self selectedItem],
|
||||
standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin],
|
||||
location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin, 0.0);
|
||||
menuOffset = [self currentValueForThemeAttribute:@"menu-offset"],
|
||||
location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin + menuOffset.width, menuOffset.height);
|
||||
|
||||
minimumWidth += standardLeftMargin;
|
||||
|
||||
@@ -847,7 +864,17 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
{
|
||||
var count = [aValue count],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
offset = [self _getInsertNullOffset];
|
||||
offset = [self _getInsertNullOffset],
|
||||
selectedBindingInfo = [_source infoForBinding:CPSelectedObjectBinding],
|
||||
selectedObject = nil;
|
||||
|
||||
if (selectedBindingInfo)
|
||||
{
|
||||
var destination = [selectedBindingInfo objectForKey:CPObservedObjectKey],
|
||||
keyPath = [selectedBindingInfo objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
selectedObject = [destination valueForKeyPath:keyPath];
|
||||
}
|
||||
|
||||
if (count + offset != [_source numberOfItems])
|
||||
{
|
||||
@@ -858,9 +885,19 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil];
|
||||
[self _setValue:[aValue objectAtIndex:i] forItem:item withOptions:options];
|
||||
var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil],
|
||||
itemValue = [aValue objectAtIndex:i];
|
||||
|
||||
[self _setValue:itemValue forItem:item withOptions:options];
|
||||
[_source addItem:item];
|
||||
|
||||
// Select this item if it is the one selected by the selected object binding
|
||||
// This is needed if the selected object binding is set before the items
|
||||
// from the content binding
|
||||
if (itemValue === selectedObject)
|
||||
{
|
||||
[_source setSelectedIndex:[_source numberOfItems] - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -463,13 +463,13 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
var midX = CGRectGetMidX(rect),
|
||||
midY = CGRectGetMidY(rect),
|
||||
endAngle = Math.PI * 2 * (([self doubleValue] - [self minValue]) / ([self maxValue] - [self minValue])) - Math.PI / 2,
|
||||
radius = MIN(rect.size.width / 2, rect.size.height / 2)
|
||||
radius = MIN(rect.size.width / 2, rect.size.height / 2);
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetLineWidth(context, borderSize);
|
||||
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"])
|
||||
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"]);
|
||||
CGContextMoveToPoint(context, midX, midY);
|
||||
CGContextAddArc(context, midX, midY, radius, 3 * Math.PI / 2, endAngle, YES)
|
||||
CGContextAddArc(context, midX, midY, radius, 3 * Math.PI / 2, endAngle, YES);
|
||||
CGContextAddLineToPoint(context, midX, midY);
|
||||
CGContextClosePath(context);
|
||||
CGContextFillPath(context);
|
||||
@@ -478,7 +478,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
else if ([self doubleValue] == [self maxValue])
|
||||
{
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"])
|
||||
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"]);
|
||||
CGContextAddEllipseInRect(context, rect);
|
||||
CGContextClosePath(context);
|
||||
CGContextFillPath(context);
|
||||
|
||||
+108
-10
@@ -67,6 +67,18 @@ CPRadioImageOffset = 4.0;
|
||||
[[button1 radioGroup] selectedRadio] returns the currently selected
|
||||
option.
|
||||
|
||||
UPDATE 09/2020 : Implementation of modern Cocoa behavior :
|
||||
|
||||
As in Cocoa, radio buttons grouping is now automatic.
|
||||
|
||||
To be associated in a common group (and so being mutually exclusive),
|
||||
radio buttons must combine 2 criteria :
|
||||
|
||||
- same superview (enclosing view)
|
||||
- same action
|
||||
|
||||
TODO: This first implementation uses "as is" CPRadioGroup. This could
|
||||
be simplified (no more need for radio group action, for example)
|
||||
*/
|
||||
@implementation CPRadio : CPButton
|
||||
{
|
||||
@@ -160,6 +172,71 @@ CPRadioImageOffset = 4.0;
|
||||
[CPApp sendAction:[_radioGroup action] to:[_radioGroup target] from:_radioGroup];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToSuperview
|
||||
{
|
||||
[self _setRadioGroup];
|
||||
[super viewDidMoveToSuperview];
|
||||
}
|
||||
|
||||
- (void)setAction:(SEL)anAction
|
||||
{
|
||||
if (anAction === _action)
|
||||
return;
|
||||
|
||||
[super setAction:anAction];
|
||||
[self _setRadioGroup];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
- (void)_setRadioGroup
|
||||
{
|
||||
// Implementation of modern Cocoa behavior : automatic radio group
|
||||
|
||||
// If no action is set or no superview, no grouping can be done.
|
||||
if (![self action] || ![self superview])
|
||||
{
|
||||
// If this radio is in a group (size > 1), remove it.
|
||||
if ([[self radioGroup] size] > 1)
|
||||
{
|
||||
[self setRadioGroup:[CPRadioGroup new]];
|
||||
|
||||
if ([self state] === CPOnState)
|
||||
[_radioGroup _setSelectedRadio:self];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Search in superview subviews for other radio buttons having the same action.
|
||||
// Take the one with the radio group having the greatest number of members.
|
||||
|
||||
var radioGroup;
|
||||
|
||||
for (var i = 0, superviewSubviews = [[self superview] subviews], count = [superviewSubviews count], aSubview, myAction = [self action], radioGroupSize = -1; (i < count); i++)
|
||||
{
|
||||
aSubview = superviewSubviews[i];
|
||||
|
||||
if ([aSubview isKindOfClass:CPRadio] && (aSubview !== self) && ([aSubview action] === myAction) && ([[aSubview radioGroup] size] > radioGroupSize))
|
||||
{
|
||||
radioGroup = [aSubview radioGroup];
|
||||
radioGroupSize = [radioGroup size];
|
||||
}
|
||||
}
|
||||
|
||||
if (radioGroup)
|
||||
[self setRadioGroup:radioGroup];
|
||||
else
|
||||
// No other radio buttons to group with found.
|
||||
// It may be because this radio button was in a radio group and its action was changed.
|
||||
// If this is the case, we must reisolate it in a new radio group.
|
||||
if ([_radioGroup size] > 1)
|
||||
[self setRadioGroup:[CPRadioGroup new]];
|
||||
|
||||
if ([self state] === CPOnState)
|
||||
[_radioGroup _setSelectedRadio:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
|
||||
@@ -183,21 +260,37 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
|
||||
[aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
#pragma mark -
|
||||
#pragma mark Override methods from CPButton
|
||||
|
||||
- (CPThemeState)_contentVisualState
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
// Note : Behavior differs from CPButton as title doesn't follow the highlightsBy content flag
|
||||
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state];
|
||||
|
||||
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
- (CPImage)alternateImage
|
||||
- (CPThemeState)_imageVisualState
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
// Note : Behavior differs from CPButton as we don't force "not selected" theme state
|
||||
// when button is highglighted and selected
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
[self highlight:YES];
|
||||
return startedTracking;
|
||||
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
|
||||
currentState = [self state];
|
||||
|
||||
if (_isHighlighted && (_highlightsBy & CPContentsCellMask))
|
||||
visualState = visualState.and(CPThemeStateHighlighted);
|
||||
|
||||
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
|
||||
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
|
||||
|
||||
return visualState;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -304,6 +397,11 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
|
||||
return _radios;
|
||||
}
|
||||
|
||||
- (int)size
|
||||
{
|
||||
return [_radios count];
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled
|
||||
{
|
||||
[_radios makeObjectsPerformSelector:@selector(setEnabled:) withObject:enabled];
|
||||
|
||||
@@ -397,7 +397,7 @@ var CPResponderNextResponderKey = @"CPResponderNextResponderKey",
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// This will come out nil on the other side with decodeObjectForKey:
|
||||
if (_nextResponder !== nil)
|
||||
if (_nextResponder != nil)
|
||||
[aCoder encodeConditionalObject:_nextResponder forKey:CPResponderNextResponderKey];
|
||||
|
||||
[aCoder encodeObject:_menu forKey:CPResponderMenuKey];
|
||||
|
||||
@@ -471,9 +471,11 @@ CPTransformableAttributeType = 1800;
|
||||
|
||||
var value;
|
||||
if (attributeType >= CPInteger16AttributeType && attributeType <= CPFloatAttributeType)
|
||||
value = [aView intValue];
|
||||
value = [aView floatValue];
|
||||
else if (attributeType == CPBooleanAttributeType)
|
||||
value = [aView state];
|
||||
else if (attributeType == CPDateAttributeType)
|
||||
value = [aView dateValue];
|
||||
else
|
||||
value = [aView stringValue];
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
|
||||
@"slice-top-border-color": [CPNull null],
|
||||
@"slice-bottom-border-color": [CPNull null],
|
||||
@"slice-last-bottom-border-color": [CPNull null],
|
||||
@"font": [CPNull null],
|
||||
@"font": [CPFont systemFontOfSize:12],
|
||||
@"font-color": [CPNull null],
|
||||
@"add-image": [CPNull null],
|
||||
@"remove-image": [CPNull null],
|
||||
@@ -157,7 +157,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self !== nil)
|
||||
if (self)
|
||||
{
|
||||
_slices = [[CPMutableArray alloc] init];
|
||||
|
||||
@@ -419,7 +419,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
|
||||
*/
|
||||
- (void)setFormattingStringsFilename:(CPString)stringsFilename
|
||||
{
|
||||
if (_standardLocalizer === nil)
|
||||
if (_standardLocalizer == nil)
|
||||
_standardLocalizer = [_CPRuleEditorLocalizer new];
|
||||
|
||||
if (_stringsFilename !== stringsFilename)
|
||||
@@ -427,14 +427,14 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
|
||||
// Convert an empty string to nil
|
||||
_stringsFilename = stringsFilename || nil;
|
||||
|
||||
if (stringsFilename !== nil)
|
||||
if (stringsFilename != nil)
|
||||
{
|
||||
if (![stringsFilename hasSuffix:@".strings"])
|
||||
stringsFilename = stringsFilename + @".strings";
|
||||
|
||||
var path = [[CPBundle mainBundle] pathForResource:stringsFilename];
|
||||
|
||||
if (path !== nil)
|
||||
if (path != nil)
|
||||
[_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]];
|
||||
}
|
||||
}
|
||||
@@ -465,7 +465,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
|
||||
*/
|
||||
- (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex
|
||||
{
|
||||
if (criteria === nil || values === nil)
|
||||
if (criteria == nil || values == nil)
|
||||
[CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."];
|
||||
|
||||
if (rowIndex < 0 || rowIndex >= [self numberOfRows])
|
||||
@@ -852,7 +852,7 @@ TODO: implement
|
||||
while (current_index !== CPNotFound)
|
||||
{
|
||||
var subpredicate = [self predicateForRow:current_index];
|
||||
if (subpredicate !== nil)
|
||||
if (subpredicate != nil)
|
||||
[subpredicates addObject:subpredicate];
|
||||
|
||||
current_index = [subrowsIndexes indexGreaterThanIndex:current_index];
|
||||
@@ -888,33 +888,33 @@ TODO: implement
|
||||
modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier],
|
||||
selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]);
|
||||
|
||||
if (lhs === nil)
|
||||
if (lhs == nil)
|
||||
{
|
||||
CPLogConsole(@"missing left expression in predicate parts dictionary");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (rhs === nil)
|
||||
if (rhs == nil)
|
||||
{
|
||||
CPLogConsole(@"missing right expression in predicate parts dictionary");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (selector === nil && operator === nil)
|
||||
if (selector == nil && operator == nil)
|
||||
{
|
||||
CPLogConsole(@"missing operator and selector in predicate parts dictionary");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (modifier === nil)
|
||||
if (modifier == nil)
|
||||
CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier");
|
||||
|
||||
if (options === nil)
|
||||
if (options == nil)
|
||||
CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption");
|
||||
|
||||
try
|
||||
{
|
||||
if (selector !== nil)
|
||||
if (selector != nil)
|
||||
predicate = [CPComparisonPredicate predicateWithLeftExpression:lhs
|
||||
rightExpression:rhs
|
||||
customSelector:selector
|
||||
@@ -1167,7 +1167,7 @@ TODO: implement
|
||||
|
||||
- (BOOL)_wantsRowAnimations
|
||||
{
|
||||
return (_currentAnimation !== nil);
|
||||
return (_currentAnimation != nil);
|
||||
}
|
||||
|
||||
- (void)_updateButtonVisibilities
|
||||
@@ -1753,7 +1753,7 @@ TODO: implement
|
||||
{
|
||||
var subpredicate = [self predicateForRow:current_index];
|
||||
|
||||
if (subpredicate !== nil)
|
||||
if (subpredicate != nil)
|
||||
[subpredicates addObject:subpredicate];
|
||||
|
||||
current_index = [subindexes indexGreaterThanIndex:current_index];
|
||||
@@ -1795,7 +1795,7 @@ TODO: implement
|
||||
startRect = [aslice frame],
|
||||
startIndex = [aslice rowIndex] - 1;
|
||||
|
||||
if ([aslice superview] === nil)
|
||||
if ([aslice superview] == nil)
|
||||
{
|
||||
startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight);
|
||||
[aslice _reconfigureSubviews];
|
||||
@@ -2129,7 +2129,7 @@ TODO: implement
|
||||
|
||||
- (BOOL)_dragShouldBeginFromMouseDown:(CPView)view
|
||||
{
|
||||
return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows === nil);
|
||||
return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows == nil);
|
||||
}
|
||||
|
||||
- (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event
|
||||
@@ -2321,7 +2321,7 @@ TODO: implement
|
||||
|
||||
- (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes
|
||||
{
|
||||
var userInfo = indexes === nil ? @{} : @{ "indexes": indexes };
|
||||
var userInfo = indexes == nil ? @{} : @{ "indexes": indexes };
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo];
|
||||
}
|
||||
|
||||
@@ -2386,7 +2386,7 @@ TODO: implement
|
||||
var criteria = [self criteriaForRow:aRow];
|
||||
indexofCriterion = [criteria indexOfObject:criterion];
|
||||
|
||||
if (parentItem !== nil
|
||||
if (parentItem != nil
|
||||
&& indexofCriterion !== CPNotFound
|
||||
&& indexofCriterion < [criteria count] - 1)
|
||||
{
|
||||
@@ -2469,7 +2469,7 @@ var CPRuleEditorAlignmentGridWidthKey = @"CPRuleEditorAlignmentGridWidth",
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
if (self !== nil)
|
||||
if (self)
|
||||
{
|
||||
[self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]];
|
||||
_alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey];
|
||||
@@ -2553,7 +2553,7 @@ var CriteriaKey = @"criteria",
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
self = [super init];
|
||||
if (self !== nil)
|
||||
if (self)
|
||||
{
|
||||
subrows = [coder decodeObjectForKey:SubrowsKey];
|
||||
criteria = [coder decodeObjectForKey:CriteriaKey];
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
{
|
||||
var title = [self title];
|
||||
|
||||
if (title !== nil)
|
||||
if (title != nil)
|
||||
return title;
|
||||
|
||||
return [self templateView];
|
||||
|
||||
@@ -40,7 +40,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
|
||||
|
||||
- (void)reloadIfNeeded
|
||||
{
|
||||
if (connection !== nil) // Connection waiting
|
||||
if (connection != nil) // Connection waiting
|
||||
{
|
||||
connection = nil;
|
||||
|
||||
@@ -51,7 +51,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
|
||||
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)rawString
|
||||
{
|
||||
if (connection !== nil && rawString !== nil)
|
||||
if (connection != nil && rawString != nil)
|
||||
[self loadContent:rawString];
|
||||
|
||||
connection = nil;
|
||||
@@ -83,11 +83,11 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
|
||||
{
|
||||
[self reloadIfNeeded];
|
||||
|
||||
if (_dictionary !== nil && aString !== nil)
|
||||
if (_dictionary != nil && aString != nil)
|
||||
{
|
||||
var localized = [_dictionary objectForKey:aString];
|
||||
|
||||
if (localized !== nil)
|
||||
if (localized != nil)
|
||||
return localized;
|
||||
}
|
||||
|
||||
|
||||
@@ -209,8 +209,7 @@
|
||||
|
||||
- (void)_reconfigureSubviews
|
||||
{
|
||||
var ruleItems,
|
||||
criteria,
|
||||
var criteria,
|
||||
repObject,
|
||||
menuItem,
|
||||
ruleView,
|
||||
@@ -366,11 +365,12 @@
|
||||
count = [_ruleOptionViews count],
|
||||
sliceFrame = [self frame],
|
||||
image = [_ruleEditor _imageAdd],
|
||||
imageSize = image ? [image size] : CGSizeMake(0,0),
|
||||
|
||||
buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - [image size].width - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - [image size].height) / 2 - 1, [image size].width, [image size].height);
|
||||
buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - imageSize.width - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - imageSize.height) / 2 - 1, imageSize.width, imageSize.height);
|
||||
|
||||
[_addButton setFrame:buttonFrame];
|
||||
buttonFrame.origin.x -= [image size].width + [self _rowButtonsInterviewHorizontalPadding];
|
||||
buttonFrame.origin.x -= imageSize.width + [self _rowButtonsInterviewHorizontalPadding];
|
||||
[_subtractButton setFrame:buttonFrame];
|
||||
|
||||
if (widthChanged)
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
CPArray _allowedFileTypes @accessors(property=allowedFileTypes);
|
||||
}
|
||||
|
||||
+ (CPURL)proposedFileURLWithDocumentName:(CPString)aDocumentName
|
||||
{
|
||||
return [CPURL URLWithString:aDocumentName];
|
||||
}
|
||||
|
||||
+ (id)savePanel
|
||||
{
|
||||
return [[CPSavePanel alloc] init];
|
||||
|
||||
+104
-25
@@ -30,6 +30,8 @@
|
||||
@import "CPScroller.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@class CPTableView
|
||||
|
||||
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
|
||||
|
||||
|
||||
@@ -147,7 +149,7 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
|
||||
|
||||
var globalValue = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPScrollersGlobalStyle"];
|
||||
|
||||
if (globalValue === nil || globalValue === -1)
|
||||
if (globalValue == nil || globalValue === -1)
|
||||
CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy
|
||||
else
|
||||
CPScrollerStyleGlobal = globalValue;
|
||||
@@ -166,33 +168,65 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
|
||||
};
|
||||
}
|
||||
|
||||
/*! Deprecated
|
||||
*/
|
||||
+ (CGSize)contentSizeForFrameSize:(CGSize)frameSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
|
||||
{
|
||||
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height) borderType:borderType],
|
||||
scrollerWidth = [CPScroller scrollerWidth];
|
||||
return [self contentSizeForFrameSize:frameSize
|
||||
horizontalScrollerClass:hFlag ? [CPScroller class] : nil
|
||||
verticalScrollerClass:vFlag ? [CPScroller class] : nil
|
||||
borderType:borderType
|
||||
controlSize:CPRegularControlSize
|
||||
scrollerStyle:CPScrollerStyleGlobal];
|
||||
}
|
||||
|
||||
if (hFlag)
|
||||
bounds.size.height -= scrollerWidth;
|
||||
+ (CGSize)contentSizeForFrameSize:(CGSize)frameSize
|
||||
horizontalScrollerClass:(Class)horizontalScrollerClass
|
||||
verticalScrollerClass:(Class)verticalScrollerClass
|
||||
borderType:(CPBorderType)borderType
|
||||
controlSize:(CPControlSize)controlSize
|
||||
scrollerStyle:(CPScrollerStyle)scrollerStyle
|
||||
{
|
||||
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height) borderType:borderType];
|
||||
|
||||
if (vFlag)
|
||||
bounds.size.width -= scrollerWidth;
|
||||
if (horizontalScrollerClass)
|
||||
bounds.size.height -= [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
|
||||
|
||||
if (verticalScrollerClass)
|
||||
bounds.size.width -= [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
|
||||
|
||||
return bounds.size;
|
||||
}
|
||||
|
||||
/*! Deprecated
|
||||
*/
|
||||
+ (CGSize)frameSizeForContentSize:(CGSize)contentSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
|
||||
{
|
||||
return [self frameSizeForContentSize:contentSize
|
||||
horizontalScrollerClass:hFlag ? [CPScroller class] : nil
|
||||
verticalScrollerClass:vFlag ? [CPScroller class] : nil
|
||||
borderType:borderType
|
||||
controlSize:CPRegularControlSize
|
||||
scrollerStyle:CPScrollerStyleGlobal];
|
||||
}
|
||||
|
||||
+ (CGSize)frameSizeForContentSize:(CGSize)contentSize
|
||||
horizontalScrollerClass:(Class)horizontalScrollerClass
|
||||
verticalScrollerClass:(Class)verticalScrollerClass
|
||||
borderType:(CPBorderType)borderType
|
||||
controlSize:(CPControlSize)controlSize
|
||||
scrollerStyle:(CPScrollerStyle)scrollerStyle
|
||||
{
|
||||
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height) borderType:borderType],
|
||||
widthInset = contentSize.width - bounds.size.width,
|
||||
heightInset = contentSize.height - bounds.size.height,
|
||||
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset),
|
||||
scrollerWidth = [CPScroller scrollerWidth];
|
||||
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset);
|
||||
|
||||
if (hFlag)
|
||||
frameSize.height += scrollerWidth;
|
||||
frameSize.height += [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
|
||||
|
||||
if (vFlag)
|
||||
frameSize.width += scrollerWidth;
|
||||
frameSize.width += [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
|
||||
|
||||
return frameSize;
|
||||
}
|
||||
@@ -220,7 +254,7 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
|
||||
/*!
|
||||
Get the system wide scroller style.
|
||||
*/
|
||||
+ (int)globalScrollerStyle
|
||||
+ (CPScrollerStyle)globalScrollerStyle
|
||||
{
|
||||
return CPScrollerStyleGlobal;
|
||||
}
|
||||
@@ -230,7 +264,7 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
|
||||
|
||||
@param aStyle the scroller style to set all scroller views to use (CPScrollerStyleLegacy or CPScrollerStyleOverlay)
|
||||
*/
|
||||
+ (void)setGlobalScrollerStyle:(int)aStyle
|
||||
+ (void)setGlobalScrollerStyle:(CPScrollerStyle)aStyle
|
||||
{
|
||||
CPScrollerStyleGlobal = aStyle;
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollerStyleGlobalChangeNotification object:nil];
|
||||
@@ -311,7 +345,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if (_delegate === nil)
|
||||
if (_delegate == nil)
|
||||
return;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(scrollViewWillScroll:)])
|
||||
@@ -321,7 +355,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
_implementedDelegateMethods |= CPScrollViewDelegate_scrollViewDidScroll_;
|
||||
}
|
||||
|
||||
- (int)scrollerStyle
|
||||
- (CPScrollerStyle)scrollerStyle
|
||||
{
|
||||
return _scrollerStyle;
|
||||
}
|
||||
@@ -332,7 +366,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
- CPScrollerStyleLegacy: Standard scrollers like Windows or Mac OS X prior to 10.7
|
||||
- CPScrollerStyleOverlay: scrollers like those in Mac OS X 10.7+
|
||||
*/
|
||||
- (void)setScrollerStyle:(int)aStyle
|
||||
- (void)setScrollerStyle:(CPScrollerStyle)aStyle
|
||||
{
|
||||
if (_scrollerStyle === aStyle)
|
||||
return;
|
||||
@@ -529,8 +563,8 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
{
|
||||
var bounds = [self _insetBounds];
|
||||
|
||||
[self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle] + 1), [CPScroller scrollerWidthInStyle:_scrollerStyle])]];
|
||||
[[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle])];
|
||||
[self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [_horizontalScroller scrollerWidth] + 1), [_horizontalScroller scrollerWidth])]];
|
||||
[[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [_horizontalScroller scrollerWidth])];
|
||||
}
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
@@ -594,8 +628,8 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
{
|
||||
var bounds = [self _insetBounds];
|
||||
|
||||
[self setVerticalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [CPScroller scrollerWidthInStyle:_scrollerStyle], MAX(CGRectGetHeight(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle] + 1))]];
|
||||
[[self verticalScroller] setFrameSize:CGSizeMake([CPScroller scrollerWidthInStyle:_scrollerStyle], CGRectGetHeight(bounds))];
|
||||
[self setVerticalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [_verticalScroller scrollerWidth], MAX(CGRectGetHeight(bounds), [_verticalScroller scrollerWidth] + 1))]];
|
||||
[[self verticalScroller] setFrameSize:CGSizeMake([_verticalScroller scrollerWidth], CGRectGetHeight(bounds))];
|
||||
}
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
@@ -911,8 +945,8 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
|
||||
bottomCornerFrame.origin.x = CGRectGetMinX(verticalFrame);
|
||||
bottomCornerFrame.origin.y = CGRectGetMaxY(verticalFrame);
|
||||
bottomCornerFrame.size.width = [CPScroller scrollerWidthInStyle:_scrollerStyle];
|
||||
bottomCornerFrame.size.height = [CPScroller scrollerWidthInStyle:_scrollerStyle];
|
||||
bottomCornerFrame.size.width = [_verticalScroller scrollerWidth];
|
||||
bottomCornerFrame.size.height = [_horizontalScroller scrollerWidth];
|
||||
|
||||
return bottomCornerFrame;
|
||||
}
|
||||
@@ -1124,8 +1158,8 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
contentFrame.size.height -= headerClipViewHeight;
|
||||
|
||||
var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)),
|
||||
verticalScrollerWidth = [CPScroller scrollerWidthInStyle:[_verticalScroller style]],
|
||||
horizontalScrollerHeight = [CPScroller scrollerWidthInStyle:[_horizontalScroller style]],
|
||||
verticalScrollerWidth = [_verticalScroller scrollerWidth],
|
||||
horizontalScrollerHeight = [_horizontalScroller scrollerWidth],
|
||||
hasVerticalScroll = difference.height > 0.0,
|
||||
hasHorizontalScroll = difference.width > 0.0,
|
||||
shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll),
|
||||
@@ -1253,7 +1287,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
}
|
||||
|
||||
if (_timerScrollersHide)
|
||||
[_timerScrollersHide invalidate]
|
||||
[_timerScrollersHide invalidate];
|
||||
|
||||
_timerScrollersHide = [CPTimer scheduledTimerWithTimeInterval:CPScrollViewFadeOutTime target:self selector:@selector(_hideScrollers:) userInfo:nil repeats:NO];
|
||||
}
|
||||
@@ -1510,6 +1544,51 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPScrollView (FirstResponder)
|
||||
|
||||
// Those 4 next methods are needed to (un)set CPThemeStateFirstResponder based on content view
|
||||
|
||||
- (void)viewWillMoveToWindow:(CPWindow)aWindow
|
||||
{
|
||||
[super viewWillMoveToWindow:aWindow];
|
||||
|
||||
[self _stopObservingFirstResponderForWindow:[self window]];
|
||||
|
||||
if (aWindow)
|
||||
[self _startObservingFirstResponderForWindow:aWindow];
|
||||
}
|
||||
|
||||
- (void)_startObservingFirstResponderForWindow:(CPWindow)aWindow
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_firstResponderDidChange:) name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
|
||||
}
|
||||
|
||||
- (void)_stopObservingFirstResponderForWindow:(CPWindow)aWindow
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
|
||||
}
|
||||
|
||||
- (void)_firstResponderDidChange:(CPNotification)aNotification
|
||||
{
|
||||
var responder = [[self window] firstResponder],
|
||||
// FIXME: We add focus ring only on table views right now. When focus ring management will be added, this must be adapted.
|
||||
shouldAddFocusRing = [responder isKindOfClass:[CPTableView class]],
|
||||
found;
|
||||
|
||||
while (!(found = (responder === self)) && responder)
|
||||
responder = [responder superview];
|
||||
|
||||
if (found && shouldAddFocusRing)
|
||||
[self setThemeState:CPThemeStateFirstResponder];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateFirstResponder];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
var CPScrollViewContentViewKey = @"CPScrollViewContentView",
|
||||
CPScrollViewHeaderClipViewKey = @"CPScrollViewHeaderClipViewKey",
|
||||
|
||||
+25
-14
@@ -62,6 +62,7 @@ NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
|
||||
NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
|
||||
|
||||
|
||||
@typedef CPScrollerStyle
|
||||
CPScrollerStyleLegacy = 0;
|
||||
CPScrollerStyleOverlay = 1;
|
||||
|
||||
@@ -134,7 +135,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
/*!
|
||||
Returns the CPScroller's width for a CPRegularControlSize.
|
||||
*/
|
||||
+ (float)scrollerWidthInStyle:(int)aStyle
|
||||
+ (float)scrollerWidthInStyle:(CPScrollerStyle)aStyle
|
||||
{
|
||||
if (!_CACHED_THEME_SCROLLER)
|
||||
_CACHED_THEME_SCROLLER = [[self alloc] init];
|
||||
@@ -208,7 +209,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
/*!
|
||||
Returns the scroller's style
|
||||
*/
|
||||
- (void)style
|
||||
- (int)style
|
||||
{
|
||||
return _style;
|
||||
}
|
||||
@@ -217,7 +218,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
Set the scroller's control size
|
||||
@param aStyle the scroller style: CPScrollerStyleLegacy or CPScrollerStyleOverlay
|
||||
*/
|
||||
- (void)setStyle:(id)aStyle
|
||||
- (void)setStyle:(CPScrollerStyle)aStyle
|
||||
{
|
||||
if (_style != nil && _style === aStyle)
|
||||
return;
|
||||
@@ -226,6 +227,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
if (_style === CPScrollerStyleLegacy)
|
||||
{
|
||||
_allowFadingOut = NO;
|
||||
[self fadeIn];
|
||||
[self setThemeState:CPThemeStateScrollViewLegacy];
|
||||
}
|
||||
@@ -258,7 +260,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
- (void)setKnobProportion:(float)aProportion
|
||||
{
|
||||
if (!_IS_NUMERIC(aProportion))
|
||||
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric"];
|
||||
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
|
||||
|
||||
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
|
||||
|
||||
@@ -316,7 +318,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
// The ordering of these tests is important. We check the knob and
|
||||
// page rects first since they may overlap with the arrows.
|
||||
|
||||
if (![self hasThemeState:CPThemeStateSelected])
|
||||
if (![self hasThemeState:CPThemeStateSelected] && ![self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
return CPScrollerNoPart;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
|
||||
@@ -380,9 +382,10 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
|
||||
slotSize = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
|
||||
minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
|
||||
knobVerticalInset = knobInset.top + knobInset.bottom,
|
||||
knobWidth = width - knobInset.left - knobInset.right,
|
||||
knobHeight = MAX(minimumKnobLength, (slotSize * _knobProportion)),
|
||||
knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight) * [self floatValue];
|
||||
knobHeight = MAX(minimumKnobLength, ((slotSize - knobVerticalInset) * _knobProportion)),
|
||||
knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight - knobVerticalInset) * [self floatValue] + knobInset.top;
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = CGRectMake(0.0, effectiveDecrementLineHeight, width, knobLocation - effectiveDecrementLineHeight);
|
||||
_partRects[CPScrollerKnob] = CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
|
||||
@@ -409,9 +412,10 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right,
|
||||
slotSize = width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
|
||||
minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
|
||||
knobWidth = MAX(minimumKnobLength, (slotSize * _knobProportion)),
|
||||
knobHorizontalInset = knobInset.left + knobInset.right,
|
||||
knobWidth = MAX(minimumKnobLength, ((slotSize - knobHorizontalInset) * _knobProportion)),
|
||||
knobHeight = height - knobInset.top - knobInset.bottom,
|
||||
knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth) * [self floatValue];
|
||||
knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth - knobHorizontalInset) * [self floatValue] + knobInset.left;
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
|
||||
_partRects[CPScrollerKnob] = CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
|
||||
@@ -460,9 +464,6 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
*/
|
||||
- (void)fadeOut
|
||||
{
|
||||
if ([self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
return;
|
||||
|
||||
[_animationScroller startAnimation];
|
||||
}
|
||||
|
||||
@@ -759,15 +760,25 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
if ([self isHidden] || ![self isEnabled] || !_isMouseOver)
|
||||
return;
|
||||
|
||||
_allowFadingOut = YES;
|
||||
_allowFadingOut = (_style !== CPScrollerStyleLegacy);
|
||||
_isMouseOver = NO;
|
||||
|
||||
if (_timerFadeOut)
|
||||
[_timerFadeOut invalidate];
|
||||
|
||||
_timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
|
||||
if ([self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
else
|
||||
_timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
|
||||
}
|
||||
|
||||
- (float)scrollerWidth
|
||||
{
|
||||
if (_style == CPScrollerStyleLegacy)
|
||||
return [self valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
|
||||
|
||||
return [self currentValueForThemeAttribute:@"scroller-width"];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Delegates
|
||||
|
||||
+369
-59
@@ -24,8 +24,12 @@
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPAnimationContext.j"
|
||||
@import "CPViewAnimator.j"
|
||||
@import "CPArrayController.j"
|
||||
|
||||
@class CPUserDefaults
|
||||
@class CALayer
|
||||
|
||||
@global CPApp
|
||||
|
||||
@@ -58,6 +62,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
BOOL _sendsSearchStringImmediately;
|
||||
BOOL _canResignFirstResponder;
|
||||
CPTimer _partialStringTimer;
|
||||
|
||||
CPView _contentView;
|
||||
BOOL _isBecomingFirstResponder;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
@@ -73,10 +80,22 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
@"image-cancel": [CPNull null],
|
||||
@"image-cancel-pressed": [CPNull null],
|
||||
@"image-search-inset" : CGInsetMake(0, 0, 0, 5),
|
||||
@"image-cancel-inset" : CGInsetMake(0, 5, 0, 0)
|
||||
@"image-cancel-inset" : CGInsetMake(0, 5, 0, 0),
|
||||
@"search-button-rect-function": [CPNull null],
|
||||
@"layout-function": [CPNull null],
|
||||
@"search-right-margin": 2,
|
||||
@"search-menu-offset": CGPointMake(10, -4)
|
||||
};
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPPredicateBinding)
|
||||
return [_CPSearchFieldPredicateBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
@@ -87,10 +106,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
_recentsAutosaveName = nil;
|
||||
|
||||
[self _init];
|
||||
#if PLATFORM(DOM)
|
||||
_cancelButton._DOMElement.style.cursor = "default";
|
||||
_searchButton._DOMElement.style.cursor = "default";
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -117,6 +132,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
[self resetSearchButton];
|
||||
|
||||
_canResignFirstResponder = YES;
|
||||
_isBecomingFirstResponder = NO;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +193,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
- (void)resetSearchButton
|
||||
{
|
||||
var button = [self searchButton],
|
||||
searchButtonImage = (_searchMenuTemplate === nil) ? [self valueForThemeAttribute:@"image-search"] : [self valueForThemeAttribute:@"image-find"];
|
||||
searchButtonImage = (_searchMenuTemplate == nil) ? [self currentValueForThemeAttribute:@"image-search"] : [self currentValueForThemeAttribute:@"image-find"];
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImageScaling:CPImageScaleAxesIndependently];
|
||||
@@ -224,8 +240,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
var button = [self cancelButton];
|
||||
[button setBordered:NO];
|
||||
[button setImageScaling:CPImageScaleAxesIndependently];
|
||||
[button setImage:[self valueForThemeAttribute:@"image-cancel"]];
|
||||
[button setAlternateImage:[self valueForThemeAttribute:@"image-cancel-pressed"]];
|
||||
[button setImage:[self currentValueForThemeAttribute:@"image-cancel"]];
|
||||
[button setAlternateImage:[self currentValueForThemeAttribute:@"image-cancel-pressed"]];
|
||||
[button setAutoresizingMask:CPViewMinXMargin];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(cancelOperation:)];
|
||||
@@ -246,8 +262,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
|
||||
if (_searchButton)
|
||||
{
|
||||
var searchBounds = [self searchButtonRectForBounds:bounds];
|
||||
leftOffset = CGRectGetMaxX(searchBounds) + 2;
|
||||
var searchBounds = [self searchButtonRectForBounds:bounds],
|
||||
rightMargin = [self _potentialCurrentValueForThemeAttribute:@"search-right-margin"];
|
||||
leftOffset = CGRectGetMaxX(searchBounds) + rightMargin;
|
||||
}
|
||||
|
||||
if (_cancelButton)
|
||||
@@ -266,10 +283,18 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
*/
|
||||
- (CGRect)searchButtonRectForBounds:(CGRect)rect
|
||||
{
|
||||
var size = [[self currentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(),
|
||||
inset = [self currentValueForThemeAttribute:@"image-search-inset"];
|
||||
var themedRectFunction = [self _potentialCurrentValueForThemeAttribute:@"search-button-rect-function"];
|
||||
|
||||
return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
|
||||
if (themedRectFunction)
|
||||
// There's a theme defined positioning function, just use it
|
||||
return objj_eval("("+themedRectFunction+")")(self, rect);
|
||||
else
|
||||
{
|
||||
var size = [[self _potentialCurrentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(),
|
||||
inset = [self _potentialCurrentValueForThemeAttribute:@"image-search-inset"];
|
||||
|
||||
return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -279,8 +304,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
*/
|
||||
- (CGRect)cancelButtonRectForBounds:(CGRect)rect
|
||||
{
|
||||
var size = [[self currentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
|
||||
inset = [self currentValueForThemeAttribute:@"image-cancel-inset"];
|
||||
var size = [[self _potentialCurrentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
|
||||
inset = [self _potentialCurrentValueForThemeAttribute:@"image-cancel-inset"];
|
||||
|
||||
return CGRectMake(CGRectGetWidth(rect) - size.width + inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height);
|
||||
}
|
||||
@@ -386,10 +411,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
*/
|
||||
- (void)setRecentSearches:(CPArray)searches
|
||||
{
|
||||
var max = MIN([self maximumRecents], [searches count]),
|
||||
searches = [searches subarrayWithRange:CPMakeRange(0, max)];
|
||||
var max = MIN([self maximumRecents], [searches count]);
|
||||
|
||||
_recentSearches = searches;
|
||||
_recentSearches = [searches subarrayWithRange:CPMakeRange(0, max)];
|
||||
[self _autosaveRecentSearchList];
|
||||
}
|
||||
|
||||
@@ -474,6 +498,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
|
||||
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[self selectAll:nil];
|
||||
[super sendAction:anAction to:anObject];
|
||||
|
||||
[_partialStringTimer invalidate];
|
||||
@@ -484,7 +509,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
|
||||
- (void)_addStringToRecentSearches:(CPString)string
|
||||
{
|
||||
if (string === nil || string === @"" || [_recentSearches containsObject:string])
|
||||
if (string == nil || string === @"" || [_recentSearches containsObject:string])
|
||||
return;
|
||||
|
||||
var searches = [CPMutableArray arrayWithArray:_recentSearches];
|
||||
@@ -531,19 +556,16 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the common case items for a recent searches menu. If there are not recent searches,
|
||||
displays a single disabled item:
|
||||
|
||||
No Recent Searches
|
||||
Provides the common case items for a recent searches menu.
|
||||
|
||||
If there are 1 more recent searches, it displays:
|
||||
|
||||
Recent Searches
|
||||
recent search 1
|
||||
recent search 2
|
||||
etc.
|
||||
Clear
|
||||
---------------------
|
||||
Clear Recent Searches
|
||||
Recent Searches
|
||||
recent search 1
|
||||
recent search 2
|
||||
etc.
|
||||
|
||||
If you wish to add items before or after the template, you can. If you put items
|
||||
before, a separator will automatically be placed before the default template item.
|
||||
@@ -568,6 +590,15 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
var template = [[CPMenu alloc] init],
|
||||
item;
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Clear"
|
||||
action:@selector(_searchFieldClearRecents:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldClearRecentsMenuItemTag];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
[self _addSeparatorToMenu:template];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
@@ -582,32 +613,20 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Clear Recent Searches"
|
||||
action:@selector(_searchFieldClearRecents:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldClearRecentsMenuItemTag];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"No Recent Searches"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldNoRecentsMenuItemTag];
|
||||
[item setEnabled:NO];
|
||||
[template addItem:item];
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
- (void)_updateSearchMenu
|
||||
{
|
||||
if (_searchMenuTemplate === nil)
|
||||
if (_searchMenuTemplate == nil)
|
||||
return;
|
||||
|
||||
var menu = [[CPMenu alloc] init],
|
||||
countOfRecents = [_recentSearches count],
|
||||
numberOfItems = [_searchMenuTemplate numberOfItems];
|
||||
|
||||
[menu setAutoenablesItems:NO];
|
||||
|
||||
for (var i = 0; i < numberOfItems; i++)
|
||||
{
|
||||
var item = [[_searchMenuTemplate itemAtIndex:i] copy];
|
||||
@@ -617,9 +636,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
case CPSearchFieldRecentsTitleMenuItemTag:
|
||||
if (countOfRecents === 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
break;
|
||||
|
||||
case CPSearchFieldRecentsMenuItemTag:
|
||||
@@ -631,7 +647,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
var recentItem = [[CPMenuItem alloc] initWithTitle:[_recentSearches objectAtIndex:recentIndex]
|
||||
action:itemAction
|
||||
keyEquivalent:[item keyEquivalent]];
|
||||
[recentItem setIndentationLevel:1];
|
||||
[item setTarget:self];
|
||||
[menu addItem:recentItem];
|
||||
}
|
||||
@@ -643,9 +658,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
if (countOfRecents === 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
|
||||
[item setAction:@selector(_searchFieldClearRecents:)];
|
||||
[item setTarget:self];
|
||||
break;
|
||||
@@ -653,9 +665,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
case CPSearchFieldNoRecentsMenuItemTag:
|
||||
if (countOfRecents !== 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -672,6 +681,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
{
|
||||
var separator = [CPMenuItem separatorItem];
|
||||
[separator setEnabled:NO];
|
||||
[separator setTag:CPSearchFieldRecentsTitleMenuItemTag];
|
||||
[aMenu addItem:separator];
|
||||
}
|
||||
|
||||
@@ -693,7 +703,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
return;
|
||||
|
||||
var aFrame = [[self superview] convertRect:[self frame] toView:nil],
|
||||
location = CGPointMake(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4);
|
||||
offset = [self currentValueForThemeAttribute:@"search-menu-offset"],
|
||||
location = CGPointMake(aFrame.origin.x + offset.x, aFrame.origin.y + aFrame.size.height + offset.y);
|
||||
|
||||
var anEvent = [CPEvent mouseEventWithType:CPRightMouseDown location:location modifierFlags:0 timestamp:[[CPApp currentEvent] timestamp] windowNumber:[[self window] windowNumber] context:nil eventNumber:1 clickCount:1 pressure:0];
|
||||
|
||||
@@ -709,10 +720,14 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
|
||||
- (void)cancelOperation:(id)sender
|
||||
{
|
||||
[self setObjectValue:@""];
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
// If something was entered, the search field must deactivate itself (else, do nothing)
|
||||
if ([[self stringValue] length] > 0)
|
||||
{
|
||||
[self setObjectValue:@""];
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
[self _updateCancelButtonVisibility];
|
||||
[[self window] makeFirstResponder:[self window]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_searchFieldSearch:(id)sender
|
||||
@@ -762,17 +777,266 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
- (void)_loadRecentSearchList
|
||||
{
|
||||
var name = [self recentsAutosaveName];
|
||||
if (name === nil)
|
||||
if (name == nil)
|
||||
return;
|
||||
|
||||
var list = [[CPUserDefaults standardUserDefaults] objectForKey:name];
|
||||
|
||||
if (list !== nil)
|
||||
if (list != nil)
|
||||
_recentSearches = list;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPSearchField (ThemingAdditions)
|
||||
|
||||
// Overwrite CPTextField method to permit themed layout functions
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (!_contentView)
|
||||
{
|
||||
// Search for the CPImageAndTextView subview of mine
|
||||
|
||||
for (var i = 0, subviews = [self subviews], nb = [subviews count]; (!_contentView && (i < nb)); i++)
|
||||
if ([subviews[i] isKindOfClass:_CPImageAndTextView])
|
||||
_contentView = subviews[i];
|
||||
}
|
||||
|
||||
var bezelColor = [self currentValueForThemeAttribute:@"bezel-color"];
|
||||
|
||||
if ([bezelColor isCSSBased])
|
||||
{
|
||||
// CSS Styling
|
||||
// We don't need bezelView as we apply CSS styling directly on the search view itself
|
||||
|
||||
[self _setBackgroundColor:bezelColor];
|
||||
|
||||
if (!_contentView)
|
||||
_contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
|
||||
[bezelView setBackgroundColor:bezelColor];
|
||||
|
||||
if (!_contentView)
|
||||
_contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
}
|
||||
|
||||
if (_contentView)
|
||||
{
|
||||
[_contentView setHidden:(_stringValue && _stringValue.length > 0) && [self hasThemeState:CPThemeStateEditing]];
|
||||
|
||||
[_contentView setText:[self hasThemeState:CPTextFieldStatePlaceholder] ? [self placeholderString] : _stringValue];
|
||||
[_contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
|
||||
[_contentView setFont:[self font]]; //[self currentValueForThemeAttribute:@"font"]];
|
||||
[_contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
|
||||
[_contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
|
||||
[_contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
|
||||
[_contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
|
||||
[_contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
|
||||
}
|
||||
|
||||
if (_isEditing)
|
||||
[self _setCSSStyleForInputElement];
|
||||
|
||||
[self updateSearchButton];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
// As CPTextField redefines setBackgroundColor, we have to bypass it
|
||||
- (void)_setBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
var grannyMethod = class_getInstanceMethod([[self superclass] superclass], @selector(setBackgroundColor:)),
|
||||
grannyImplementation = method_getImplementation(grannyMethod);
|
||||
|
||||
grannyImplementation(self, @selector(setBackgroundColor:), aColor);
|
||||
}
|
||||
|
||||
- (void)_layoutSubviews
|
||||
{
|
||||
var themedLayoutFunction = [self currentValueForThemeAttribute:@"layout-function"];
|
||||
|
||||
if (themedLayoutFunction)
|
||||
// There's a theme defined layout function, just use it
|
||||
objj_eval("("+themedLayoutFunction+")")(self);
|
||||
else
|
||||
// Call directly the completion handler
|
||||
[self themedLayoutFunctionCompletionHandler];
|
||||
}
|
||||
|
||||
- (void)updateSearchButton
|
||||
{
|
||||
[_searchButton setImage:(_searchMenuTemplate ? [self currentValueForThemeAttribute:@"image-find"] : [self currentValueForThemeAttribute:@"image-search"])];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)themedLayoutFunctionCompletionHandler
|
||||
{
|
||||
if (_isBecomingFirstResponder)
|
||||
{
|
||||
_isBecomingFirstResponder = NO;
|
||||
|
||||
// Call the real _becomeFirstKeyResponder to finish setting the input element
|
||||
[super _becomeFirstKeyResponder];
|
||||
|
||||
// For some (unknown yet) reason, input field doesn't focus and text field visual state is not updated
|
||||
var element = [self _inputElement];
|
||||
element.focus();
|
||||
[self layoutSubviews];
|
||||
[CALayer runLoopUpdateLayers]; // Thank you @daboe01 for suggesting adding this
|
||||
}
|
||||
else
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
// We override CPTextField method in order to delay the display of the input element until the end of the animation
|
||||
- (BOOL)_becomeFirstKeyResponder
|
||||
{
|
||||
// As we have to return a result, we check if response could be NO
|
||||
|
||||
if (![self _isWithinUsablePlatformRect] || ![self isEditable])
|
||||
return NO;
|
||||
|
||||
// We are now sure that response will be YES
|
||||
_isBecomingFirstResponder = YES;
|
||||
|
||||
// We have to do this now in order to avoid running conditions messing things among multiple textfields
|
||||
_stringValue = [self stringValue];
|
||||
var element = [self _inputElement];
|
||||
element.value = _stringValue;
|
||||
|
||||
[self _layoutSubviews];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textDidEndEditing:(CPNotification)note
|
||||
{
|
||||
if ([note object] != self)
|
||||
return;
|
||||
|
||||
[self _layoutSubviews];
|
||||
[super textDidEndEditing:note];
|
||||
}
|
||||
|
||||
- (void)_windowDidResignKey:(CPNotification)aNotification
|
||||
{
|
||||
// When the window resigns key, if the search field is empty, it must cancel first responder
|
||||
if ([[self stringValue] length] == 0)
|
||||
[[self window] makeFirstResponder:[self window]];
|
||||
|
||||
[super _windowDidResignKey:aNotification];
|
||||
[self _layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderString:(CPString)aStringValue
|
||||
{
|
||||
[super setPlaceholderString:aStringValue];
|
||||
[self _layoutContent];
|
||||
}
|
||||
|
||||
- (void)_layoutContent
|
||||
{
|
||||
[_searchButton setFrame:[self searchButtonRectForBounds:[self bounds]]];
|
||||
[_contentView setFrame:[self contentRectForBounds:[self bounds]]];
|
||||
[_cancelButton setFrame:[self cancelButtonRectForBounds:[self bounds]]];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
[self _layoutContent];
|
||||
}
|
||||
|
||||
- (id)_potentialCurrentValueForThemeAttribute:(CPString)aName
|
||||
{
|
||||
if (_isBecomingFirstResponder)
|
||||
return [self valueForThemeAttribute:aName inState:[self themeState].and(CPThemeStateEditing)];
|
||||
else
|
||||
return [self currentValueForThemeAttribute:aName];
|
||||
}
|
||||
|
||||
- (void)unbind:(CPString)aBinding
|
||||
{
|
||||
[super unbind:aBinding];
|
||||
|
||||
// our CPPredicateBinding binder adds a binding to the value.
|
||||
// this private binding has to be also removed
|
||||
if (aBinding === CPPredicateBinding)
|
||||
[[[self class] _binderClassForBinding:aBinding] unbind:CPValueBinding forObject:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPSearchField (CPTrackingArea)
|
||||
{
|
||||
CPTrackingArea _searchButtonTrackingArea;
|
||||
CPTrackingArea _cancelButtonTrackingArea;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (_searchButtonTrackingArea)
|
||||
{
|
||||
[self removeTrackingArea:_searchButtonTrackingArea];
|
||||
_searchButtonTrackingArea = nil;
|
||||
}
|
||||
|
||||
if (_cancelButtonTrackingArea)
|
||||
{
|
||||
[self removeTrackingArea:_cancelButtonTrackingArea];
|
||||
_cancelButtonTrackingArea = nil;
|
||||
}
|
||||
|
||||
if (_searchButton)
|
||||
{
|
||||
_searchButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_searchButton frame]
|
||||
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
|
||||
owner:self
|
||||
userInfo:@{ @"isButton": YES }];
|
||||
|
||||
[self addTrackingArea:_searchButtonTrackingArea];
|
||||
}
|
||||
|
||||
if (_cancelButton && ![_cancelButton isHidden])
|
||||
{
|
||||
_cancelButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_cancelButton frame]
|
||||
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
|
||||
owner:self
|
||||
userInfo:@{ @"isButton": YES }];
|
||||
|
||||
[self addTrackingArea:_cancelButtonTrackingArea];
|
||||
}
|
||||
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
if ([[[anEvent trackingArea] userInfo] objectForKey:@"isButton"])
|
||||
[[CPCursor arrowCursor] set];
|
||||
else
|
||||
[super cursorUpdate:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
|
||||
CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
|
||||
CPSendsSearchStringImmediatelyKey = @"CPSendsSearchStringImmediatelyKey",
|
||||
@@ -825,3 +1089,49 @@ var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPSearchFieldPredicateBinder : CPBinder
|
||||
{
|
||||
CPArrayController _controller;
|
||||
CPString _predicateFormat;
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPPredicateBinding)
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey];
|
||||
|
||||
_controller = [_info objectForKey:CPObservedObjectKey];
|
||||
_predicateFormat = [options objectForKey:"CPPredicateFormat"];
|
||||
[_source bind:CPValueBinding toObject:self withKeyPath:"searchFieldValue" options:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSearchFieldValue:(CPString)aValue
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
|
||||
if (aValue)
|
||||
{
|
||||
var values = @[],
|
||||
formatString = _predicateFormat.replace(/\$value/g, function(x) {[values addObject:aValue]; return "%@";});
|
||||
|
||||
[_controller setFilterPredicate:[CPPredicate predicateWithFormat:formatString argumentArray:values]];
|
||||
}
|
||||
else
|
||||
[_controller setFilterPredicate:nil];
|
||||
|
||||
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
}
|
||||
|
||||
- (CPString)searchFieldValue
|
||||
{
|
||||
return [_source stringValue];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+59
-49
@@ -604,7 +604,12 @@ CPSegmentSwitchTrackingMomentary = 2;
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if ([aName hasPrefix:@"segment-content"])
|
||||
return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
|
||||
{
|
||||
var view = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
|
||||
[view _setUsesSingleLineMode:YES];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
return [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
}
|
||||
@@ -797,7 +802,8 @@ CPSegmentSwitchTrackingMomentary = 2;
|
||||
label = [segment label],
|
||||
image = [segment image];
|
||||
|
||||
width = (label ? [label sizeWithFont:[self font]].width : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
|
||||
// add 1 pixel to account for possible fractional pixels at right edge
|
||||
width = (label ? [label sizeWithFont:[self font]].width + 1 : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
|
||||
}
|
||||
|
||||
return CGRectMake(left, top, width, height);
|
||||
@@ -867,64 +873,68 @@ CPSegmentSwitchTrackingMomentary = 2;
|
||||
var type = [anEvent type],
|
||||
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (type == CPLeftMouseUp)
|
||||
switch (type)
|
||||
{
|
||||
if (_trackingSegment == -1)
|
||||
case CPLeftMouseUp:
|
||||
|
||||
if (_trackingSegment === CPNotFound)
|
||||
return;
|
||||
|
||||
if (_trackingSegment === [self testSegment:location])
|
||||
{
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
|
||||
{
|
||||
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
|
||||
|
||||
// With ANY, _selectedSegment means last pressed.
|
||||
_selectedSegment = _trackingSegment;
|
||||
}
|
||||
else
|
||||
[self setSelected:YES forSegment:_trackingSegment];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
|
||||
{
|
||||
[self setSelected:NO forSegment:_trackingSegment];
|
||||
|
||||
_selectedSegment = CPNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:NO];
|
||||
|
||||
_trackingSegment = CPNotFound;
|
||||
|
||||
return;
|
||||
|
||||
if (_trackingSegment === [self testSegment:location])
|
||||
{
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
|
||||
case CPLeftMouseDown:
|
||||
|
||||
var trackingSegment = [self testSegment:location];
|
||||
if (trackingSegment > CPNotFound && [self isEnabledForSegment:trackingSegment])
|
||||
{
|
||||
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
|
||||
|
||||
// With ANY, _selectedSegment means last pressed.
|
||||
_selectedSegment = _trackingSegment;
|
||||
_trackingHighlighted = YES;
|
||||
_trackingSegment = trackingSegment;
|
||||
[self drawSegmentBezel:_trackingSegment highlight:YES];
|
||||
}
|
||||
else
|
||||
[self setSelected:YES forSegment:_trackingSegment];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
break;
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
|
||||
case CPLeftMouseDragged:
|
||||
|
||||
if (_trackingSegment === CPNotFound)
|
||||
return;
|
||||
|
||||
var highlighted = [self testSegment:location] === _trackingSegment;
|
||||
|
||||
if (highlighted != _trackingHighlighted)
|
||||
{
|
||||
[self setSelected:NO forSegment:_trackingSegment];
|
||||
_trackingHighlighted = highlighted;
|
||||
|
||||
_selectedSegment = CPNotFound;
|
||||
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:NO];
|
||||
|
||||
_trackingSegment = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == CPLeftMouseDown)
|
||||
{
|
||||
var trackingSegment = [self testSegment:location];
|
||||
if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
|
||||
{
|
||||
_trackingHighlighted = YES;
|
||||
_trackingSegment = trackingSegment;
|
||||
[self drawSegmentBezel:_trackingSegment highlight:YES];
|
||||
}
|
||||
}
|
||||
|
||||
else if (type == CPLeftMouseDragged)
|
||||
{
|
||||
if (_trackingSegment == -1)
|
||||
return;
|
||||
|
||||
var highlighted = [self testSegment:location] === _trackingSegment;
|
||||
|
||||
if (highlighted != _trackingHighlighted)
|
||||
{
|
||||
_trackingHighlighted = highlighted;
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
+802
-150
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@
|
||||
|
||||
@end
|
||||
|
||||
@global document
|
||||
|
||||
var CPSoundDelegate_sound_didFinishPlaying_ = 1 << 1;
|
||||
|
||||
|
||||
+1223
-803
File diff suppressed because it is too large
Load Diff
+7
-6
@@ -188,12 +188,12 @@
|
||||
[_buttonUp setFrame:upFrame];
|
||||
[_buttonDown setFrame:downFrame];
|
||||
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
|
||||
}
|
||||
|
||||
- (void)_sizeToFit
|
||||
@@ -323,6 +323,7 @@ var CPStepperMinValue = @"CPStepperMinValue",
|
||||
_autorepeat = [aCoder decodeBoolForKey:CPStepperAutorepeat];
|
||||
|
||||
[self _init];
|
||||
[self setEnabled:[self isEnabled]];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -24,11 +24,15 @@
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPPlatformString.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
|
||||
var CPStringSizeWithFontInWidthCache = {};
|
||||
var CPStringSizeWithFontInWidthCache = [],
|
||||
CPStringSizeWithFontHeightCache = [],
|
||||
CPStringSizeMeasuringContext;
|
||||
|
||||
CPStringSizeCachingEnabled = YES;
|
||||
CPCanvasStringSizingIsFunctional = NO;
|
||||
|
||||
@implementation CPString (CPStringDrawing)
|
||||
|
||||
@@ -53,20 +57,66 @@ CPStringSizeCachingEnabled = YES;
|
||||
return [self sizeWithFont:aFont inWidth:NULL];
|
||||
}
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
if ([self class] != [CPString class])
|
||||
return;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
|
||||
{
|
||||
if (!CPStringSizeMeasuringContext)
|
||||
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate();
|
||||
|
||||
// This is to make sure that Canvas based string sizing is functional before we use it.
|
||||
// Currently, Chrome has issues with certain strings, FF had issues in the past.
|
||||
// Unfortunately, this test does fit in CPCompatibility.j where things are not sufficiently initialized.
|
||||
|
||||
var testingFont = [CPFont systemFontOfSize:12];
|
||||
var testingText = 'A A A A A A A A';
|
||||
CPStringSizeMeasuringContext.font = [testingFont cssString];
|
||||
CPCanvasStringSizingIsFunctional = ROUND(CPStringSizeMeasuringContext.measureText(testingText).width) == ROUND([CPPlatformString sizeOfString:testingText withFont:testingFont forWidth:NULL].width);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
|
||||
{
|
||||
if (!CPStringSizeCachingEnabled)
|
||||
return [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
|
||||
var size;
|
||||
|
||||
var cacheKey = self + [aFont cssString] + aWidth,
|
||||
size = CPStringSizeWithFontInWidthCache[cacheKey];
|
||||
#if PLATFORM(DOM)
|
||||
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
|
||||
|
||||
if (size === undefined)
|
||||
{
|
||||
if (sizeCacheForFont === undefined)
|
||||
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
|
||||
|
||||
var cssString = [aFont cssString],
|
||||
cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
|
||||
|
||||
size = sizeCacheForFont[cacheKey];
|
||||
|
||||
if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey))
|
||||
return CGSizeMakeCopy(size);
|
||||
|
||||
if (!CPCanvasStringSizingIsFunctional || aWidth)
|
||||
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
|
||||
CPStringSizeWithFontInWidthCache[cacheKey] = size;
|
||||
else
|
||||
{
|
||||
if (CPPlatformHasBug(CPTextSizingAlwaysNeedsSetFontBug) || CPStringSizeMeasuringContext.font !== cssString)
|
||||
CPStringSizeMeasuringContext.font = cssString;
|
||||
|
||||
var fontHeight = CPStringSizeWithFontHeightCache[cssString];
|
||||
|
||||
if (fontHeight === undefined)
|
||||
fontHeight = CPStringSizeWithFontHeightCache[cssString] = [aFont defaultLineHeightForFont];
|
||||
|
||||
size = CGSizeMake(CPStringSizeMeasuringContext.measureText(self).width, fontHeight);
|
||||
}
|
||||
|
||||
sizeCacheForFont[cacheKey] = size;
|
||||
#else
|
||||
size = CGSizeMake(0, 0);
|
||||
#endif
|
||||
return CGSizeMakeCopy(size);
|
||||
}
|
||||
|
||||
|
||||
+234
-28
@@ -35,6 +35,7 @@ CPNoTabsLineBorder = 5; //Has no tabs and displays a line border.
|
||||
CPNoTabsNoBorder = 6; //Displays no tabs and no border.
|
||||
|
||||
@class _CPTabViewBox
|
||||
@class _CPSegmentedControl
|
||||
|
||||
var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
CPTabViewShouldSelectTabViewItemSelector = 1 << 2,
|
||||
@@ -65,9 +66,9 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
{
|
||||
CPArray _items;
|
||||
|
||||
CPSegmentedControl _tabs;
|
||||
_CPSegmentedControl _tabs;
|
||||
_CPTabViewBox _box;
|
||||
CPView _placeHolderView;
|
||||
CPView _placeholderView @accessors;
|
||||
|
||||
CPTabViewItem _selectedTabViewItem;
|
||||
|
||||
@@ -78,6 +79,20 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
unsigned _delegateSelectors;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"tab-view";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"nib2cib-adjustment-frame": [CPNull null],
|
||||
@"should-center-on-border": NO,
|
||||
@"box-content-inset": CGInsetMakeZero()
|
||||
};
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
@@ -92,21 +107,25 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_tabs = [[CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()];
|
||||
_tabs = [[_CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()];
|
||||
[_tabs setTabView:self];
|
||||
[_tabs setHitTests:NO];
|
||||
[_tabs setSegments:[CPArray array]];
|
||||
[_tabs setAction:@selector(_reflectSelectedTab:)];
|
||||
[_tabs setTarget:self];
|
||||
|
||||
var height = [_tabs valueForThemeAttribute:@"min-size"].height;
|
||||
[_tabs setFrameSize:CGSizeMake(0, height)];
|
||||
|
||||
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
|
||||
[_box setTabView:self];
|
||||
[self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
|
||||
[_box setContentInset:[self currentValueForThemeAttribute:@"box-content-inset"]];
|
||||
[_box setContentViewMargins:CGSizeMakeZero()];
|
||||
|
||||
[self addSubview:_box];
|
||||
[self addSubview:_tabs];
|
||||
|
||||
_placeHolderView = nil;
|
||||
_placeholderView = nil;
|
||||
}
|
||||
|
||||
- (CPArray)items
|
||||
@@ -131,11 +150,13 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
*/
|
||||
- (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
|
||||
{
|
||||
[self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
|
||||
[self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex] canUpdateSelectedTab:YES];
|
||||
}
|
||||
|
||||
- (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes
|
||||
- (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes canUpdateSelectedTab:(BOOL)canUpdateSelectedTab
|
||||
{
|
||||
var prevItemsCount = [self numberOfTabViewItems];
|
||||
|
||||
[_tabs insertSegments:tabViewItems atIndexes:indexes];
|
||||
[tabViewItems makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
|
||||
|
||||
@@ -143,6 +164,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
[self _reverseSetContent];
|
||||
|
||||
[self _sendDelegateTabViewDidChangeNumberOfTabViewItems];
|
||||
|
||||
// Do not allow empty selection if selection bindings are not enabled.
|
||||
if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded] && canUpdateSelectedTab)
|
||||
[self _selectTabViewItemAtIndex:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -169,7 +194,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
- (void)_didRemoveTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPInteger)idx
|
||||
{
|
||||
// If the selection is managed by bindings, let the binder do that.
|
||||
if ([self binderForBinding:CPSelectionIndexesBinding] || [self binderForBinding:CPSelectedIndexBinding])
|
||||
if ([self _isSelectionBinded])
|
||||
return;
|
||||
|
||||
if (_selectedTabViewItem == aTabViewItem)
|
||||
@@ -268,7 +293,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
*/
|
||||
- (void)selectNextTabViewItem:(id)aSender
|
||||
{
|
||||
if (_selectedTabViewItem === nil)
|
||||
if (_selectedTabViewItem == nil)
|
||||
return;
|
||||
|
||||
var nextIndex = [self indexOfTabViewItem:_selectedTabViewItem] + 1;
|
||||
@@ -286,7 +311,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
*/
|
||||
- (void)selectPreviousTabViewItem:(id)aSender
|
||||
{
|
||||
if (_selectedTabViewItem === nil)
|
||||
if (_selectedTabViewItem == nil)
|
||||
return;
|
||||
|
||||
var previousIndex = [self indexOfTabViewItem:_selectedTabViewItem] - 1;
|
||||
@@ -335,13 +360,40 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
[_tabs setSelectedSegment:anIndex];
|
||||
_selectedTabViewItem = aTabViewItem;
|
||||
[self _displayItemView:[aTabViewItem view]];
|
||||
[self _loadTabViewItem:aTabViewItem];
|
||||
|
||||
[self _sendDelegateDidSelectTabViewItem:aTabViewItem];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_loadTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
var controller = [aTabViewItem viewController];
|
||||
|
||||
if (controller != nil && ![controller isViewLoaded])
|
||||
{
|
||||
[controller loadViewWithCompletionHandler:function(view, error)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
CPLog.warn("Could not load the view for item " + aTabViewItem + ". " + error);
|
||||
}
|
||||
else if (view != nil)
|
||||
{
|
||||
[aTabViewItem setView:view];
|
||||
|
||||
if ([self selectedTabViewItem] == aTabViewItem)
|
||||
[self _displayItemView:view];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self _displayItemView:[aTabViewItem view]];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current item being displayed.
|
||||
@return the tab view item currenly being displayed by the receiver
|
||||
@@ -429,10 +481,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
{
|
||||
var aFrame = [self frame],
|
||||
segmentedHeight = CGRectGetHeight([_tabs frame]),
|
||||
origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
|
||||
borderWidth = [self currentValueForThemeAttribute:@"should-center-on-border"] ? [_box borderWidth] : 0,
|
||||
origin = _type === CPTopTabsBezelBorder ? (segmentedHeight - borderWidth) / 2 : 0;
|
||||
|
||||
[_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame),
|
||||
CGRectGetHeight(aFrame) - segmentedHeight / 2)];
|
||||
[_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame), CGRectGetHeight(aFrame) - (segmentedHeight - borderWidth) / 2)];
|
||||
|
||||
[self _repositionTabs];
|
||||
}
|
||||
@@ -494,10 +546,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var segmentIndex = [_tabs testSegment:[_tabs convertPoint:[anEvent locationInWindow] fromView:nil]];
|
||||
|
||||
if (segmentIndex != CPNotFound && [self selectTabViewItemAtIndex:segmentIndex])
|
||||
[_tabs trackSegment:anEvent];
|
||||
[_tabs trackSegment:anEvent];
|
||||
}
|
||||
|
||||
- (void)_repositionTabs
|
||||
@@ -516,6 +565,11 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
[_box setContentView:aView];
|
||||
}
|
||||
|
||||
- (void)_reflectSelectedTab:(id)aSender
|
||||
{
|
||||
[self selectTabViewItemAtIndex:[_tabs selectedSegment]];
|
||||
}
|
||||
|
||||
// DELEGATE METHODS
|
||||
|
||||
- (BOOL)_sendDelegateShouldSelectTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
@@ -573,7 +627,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
{
|
||||
var theBinder = [self binderForBinding:CPSelectionIndexesBinding];
|
||||
|
||||
if (theBinder !== nil)
|
||||
if (theBinder != nil)
|
||||
[theBinder reverseSetValueFor:@"selectionIndexes"];
|
||||
else
|
||||
{
|
||||
@@ -584,10 +638,15 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
- (CPBinder)binderForBinding:(CPString)aBinding
|
||||
{
|
||||
var cls = [[self class] _binderClassForBinding:aBinding]
|
||||
var cls = [[self class] _binderClassForBinding:aBinding];
|
||||
return [cls getBinding:aBinding forObject:self];
|
||||
}
|
||||
|
||||
- (BOOL)_isSelectionBinded
|
||||
{
|
||||
return [self binderForBinding:CPSelectionIndexesBinding] || [self binderForBinding:CPSelectedIndexBinding];
|
||||
}
|
||||
|
||||
- (void)setItems:(CPArray)tabViewItems
|
||||
{
|
||||
if ([tabViewItems isEqualToArray:[_tabs segments]])
|
||||
@@ -614,17 +673,17 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
- (void)_displayPlaceholder:(CPString)aPlaceholder
|
||||
{
|
||||
if (_placeHolderView == nil)
|
||||
if (_placeholderView == nil)
|
||||
{
|
||||
_placeHolderView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
_placeholderView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
[textField setTag:1000];
|
||||
[textField setTextColor:[CPColor whiteColor]];
|
||||
[textField setFont:[CPFont boldFontWithName:@"Geneva" size:18 italic:YES]];
|
||||
[_placeHolderView addSubview:textField];
|
||||
[_placeholderView addSubview:textField];
|
||||
}
|
||||
|
||||
var textField = [_placeHolderView viewWithTag:1000];
|
||||
var textField = [_placeholderView viewWithTag:1000];
|
||||
[textField setStringValue:aPlaceholder];
|
||||
[textField sizeToFit];
|
||||
|
||||
@@ -634,7 +693,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
[textField setFrameOrigin:origin];
|
||||
|
||||
[self _displayItemView:_placeHolderView];
|
||||
[self _displayItemView:_placeholderView];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -765,7 +824,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
|
||||
[_tabs setFont:_font];
|
||||
|
||||
var items = [aCoder decodeObjectForKey:CPTabViewItemsKey] || [CPArray array];
|
||||
[self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])]];
|
||||
[self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])] canUpdateSelectedTab:NO];
|
||||
|
||||
[self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
|
||||
|
||||
@@ -827,15 +886,48 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPTabView (CSSTheming)
|
||||
|
||||
#pragma mark Override
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[self setTheme:aTheme];
|
||||
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
|
||||
|
||||
// Items must also perform this (without this, only the selected item does)
|
||||
|
||||
for (var i = 0, allItems = [self items], count = allItems.length; i < count; i++)
|
||||
if (allItems[i] != _selectedTabViewItem)
|
||||
[[allItems[i] view] _setThemeIncludingDescendants:aTheme];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation _CPTabViewBox : CPBox
|
||||
{
|
||||
CPTabView _tabView @accessors(property=tabView);
|
||||
CPTabView _tabView @accessors(property=tabView);
|
||||
CGInset _contentInset @accessors(property=contentInset);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Override
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
[self setBoxType:CPBoxPrimary];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPView)hitTest:(CGPoint)aPoint
|
||||
{
|
||||
// Here we check if we have clicked on the segmentedControl of the tabView or not
|
||||
@@ -848,4 +940,118 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
|
||||
return [super hitTest:aPoint];
|
||||
}
|
||||
|
||||
@end
|
||||
// Some CPBox overrides because CPTabView use of CPBox differs from legacy CPBox regarding layout.
|
||||
|
||||
- (void)setContentView:(CPView)aView
|
||||
{
|
||||
if (aView === _contentView)
|
||||
return;
|
||||
|
||||
if (_contentInset)
|
||||
[aView setFrame:CGRectInsetByInset([self bounds], _contentInset)];
|
||||
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
// A nil contentView is allowed (tested in Cocoa 2013-02-22).
|
||||
if (!aView)
|
||||
[_contentView removeFromSuperview];
|
||||
else if (_contentView)
|
||||
[_boxView replaceSubview:_contentView with:aView];
|
||||
else
|
||||
[_boxView addSubview:aView];
|
||||
|
||||
_contentView = aView;
|
||||
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
- (void)sizeToFit
|
||||
{
|
||||
var offset = [self _titleHeightOffset],
|
||||
size = [self frameSize];
|
||||
|
||||
[_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
|
||||
|
||||
if (!_contentView)
|
||||
return;
|
||||
|
||||
var boxSize = [_boxView frameSize];
|
||||
|
||||
if (_contentInset)
|
||||
[_contentView setFrame:CGRectMake(_contentInset.left, _contentInset.top, boxSize.width - _contentInset.left - _contentInset.right, boxSize.height - _contentInset.top - _contentInset.bottom)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
# pragma mark -
|
||||
|
||||
// This subclass of CPSegmentedControl implements specific behaviour needed by CPTabView, which
|
||||
// differs in some ways from normal CPSegmentedControl habits :
|
||||
//
|
||||
// - all items are enabled
|
||||
// - when you select a tab (mouse down, holding) and drag upon another tab, the second one reacts
|
||||
// (showing a pushed state) and can then be selected by releasing the mouse over it.
|
||||
// - a delegate has the opportunity to accept or not the selection of an item
|
||||
|
||||
@implementation _CPSegmentedControl : CPSegmentedControl
|
||||
{
|
||||
CPTabView _tabView @accessors(property=tabView);
|
||||
}
|
||||
|
||||
- (void)trackSegment:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type],
|
||||
location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
currentSegment = [self testSegment:location];
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CPLeftMouseUp:
|
||||
|
||||
if (_trackingSegment === CPNotFound)
|
||||
return;
|
||||
|
||||
if ((_trackingSegment !== _selectedSegment) &&
|
||||
[_tabView _sendDelegateShouldSelectTabViewItem:[_tabView tabViewItemAtIndex:_trackingSegment]])
|
||||
{
|
||||
[self setSelected:YES forSegment:_trackingSegment];
|
||||
_selectedSegment = _trackingSegment;
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:NO];
|
||||
|
||||
_trackingSegment = CPNotFound;
|
||||
|
||||
return;
|
||||
|
||||
case CPLeftMouseDown:
|
||||
|
||||
if (currentSegment > CPNotFound)
|
||||
{
|
||||
_trackingSegment = currentSegment;
|
||||
[self drawSegmentBezel:_trackingSegment highlight:YES];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CPLeftMouseDragged:
|
||||
|
||||
if (_trackingSegment !== currentSegment)
|
||||
{
|
||||
if (_trackingSegment > CPNotFound)
|
||||
[self drawSegmentBezel:_trackingSegment highlight:NO];
|
||||
|
||||
_trackingSegment = currentSegment;
|
||||
|
||||
if (_trackingSegment > CPNotFound)
|
||||
[self drawSegmentBezel:_trackingSegment highlight:YES];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ CPPressedTab = 2;
|
||||
[self setLabel:title];
|
||||
|
||||
if ([_tabView selectedTabViewItem] == self)
|
||||
[_tabView _displayItemView:[_viewController view]];
|
||||
[_tabView _loadTabViewItem:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+65
-11
@@ -287,7 +287,7 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
if (width < [self minWidth])
|
||||
[self setMinWidth:width];
|
||||
else if (width > [self maxWidth])
|
||||
[self setMaxWidth:width]
|
||||
[self setMaxWidth:width];
|
||||
|
||||
if (_width !== width)
|
||||
[self setWidth:width];
|
||||
@@ -499,7 +499,7 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
*/
|
||||
- (void)setHidden:(BOOL)shouldBeHidden
|
||||
{
|
||||
shouldBeHidden = !!shouldBeHidden
|
||||
shouldBeHidden = !!shouldBeHidden;
|
||||
|
||||
if (_isHidden === shouldBeHidden)
|
||||
return;
|
||||
@@ -537,6 +537,14 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
return _headerToolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:"<%@ 0x%@ identifier=%@>", [self className], [CPString stringWithHash:[self UID]], [self identifier]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTableColumnValueBinder : CPBinder
|
||||
@@ -585,7 +593,7 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey],
|
||||
optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
|
||||
return optionValue === nil ? YES : [optionValue boolValue];
|
||||
return optionValue == nil ? YES : [optionValue boolValue];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -631,6 +639,53 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
This method will return the object at a row in the first found CPArray in the key path
|
||||
that is divided in a first and second part.
|
||||
The first part is never a combined key path. The second part can be a combined key path.
|
||||
If this optimization is not done we will create an array with the valueForKeyPath value on each row and then pick
|
||||
the wanted value for the row and throw away all the other rows. It is much more effective to first
|
||||
pick the row and then do the valueForKeyPath on the rest of the key path.
|
||||
When the second part is depleated it will stop the search for a CPArray and return the
|
||||
current object for the first part. The second part will then be nil.
|
||||
The secondPartRef will always be updated with the rest of the key path that can be applied
|
||||
to the returned object.
|
||||
It will stop the search if the object is nil
|
||||
*/
|
||||
- (CPValueCoding)_firstObjectInArrayUsingKeyPathFirstPart:(CPString)firstPart secondPart:(CPStringRef)secondPartRef sourceObject:(CPValueCoding)source forRow:(unsigned)aRow
|
||||
{
|
||||
var firstValue = [source valueForKeyPath:firstPart];
|
||||
|
||||
if (firstValue == nil)
|
||||
return firstValue;
|
||||
|
||||
if ([firstValue isKindOfClass:CPArray])
|
||||
return [firstValue objectAtIndex:aRow];
|
||||
|
||||
var secondPart = @deref(secondPartRef);
|
||||
|
||||
if (secondPart == nil)
|
||||
return firstValue;
|
||||
|
||||
var dotIndex = secondPart.indexOf(".");
|
||||
|
||||
if (dotIndex === CPNotFound)
|
||||
{
|
||||
firstPart = secondPart;
|
||||
@deref(secondPartRef) = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstPart = secondPart.substring(0, dotIndex);
|
||||
@deref(secondPartRef) = secondPart.substring(dotIndex + 1);
|
||||
}
|
||||
|
||||
return [self _firstObjectInArrayUsingKeyPathFirstPart:firstPart secondPart:secondPartRef sourceObject:firstValue forRow:aRow];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
@@ -647,7 +702,7 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
bindingInfo = binding._info,
|
||||
destination = [bindingInfo objectForKey:CPObservedObjectKey],
|
||||
keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
|
||||
dotIndex = keyPath.lastIndexOf("."),
|
||||
dotIndex = keyPath.indexOf("."),
|
||||
value;
|
||||
|
||||
if (dotIndex === CPNotFound)
|
||||
@@ -662,17 +717,16 @@ CPTableColumnUserResizingMask = 1 << 1;
|
||||
|
||||
The optimization is to get the array and access the value directly. This
|
||||
turns the operation into a single access regardless of how long the model
|
||||
array is.
|
||||
array is or how long the key path is.
|
||||
*/
|
||||
|
||||
var firstPart = keyPath.substring(0, dotIndex),
|
||||
secondPart = keyPath.substring(dotIndex + 1),
|
||||
firstValue = [destination valueForKeyPath:firstPart];
|
||||
secondPart = keyPath.substring(dotIndex + 1);
|
||||
|
||||
if ([firstValue isKindOfClass:CPArray])
|
||||
value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
|
||||
else
|
||||
value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow];
|
||||
value = [self _firstObjectInArrayUsingKeyPathFirstPart:firstPart secondPart:@ref(secondPart) sourceObject:destination forRow:aRow];
|
||||
|
||||
if (secondPart != nil)
|
||||
value = [value valueForKeyPath:secondPart];
|
||||
}
|
||||
|
||||
value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
|
||||
|
||||
+35
-15
@@ -89,7 +89,7 @@
|
||||
var inset = [self currentValueForThemeAttribute:@"text-inset"],
|
||||
bounds = [self bounds];
|
||||
|
||||
[_textField setFrame:CGRectMake(inset.right, inset.top, bounds.size.width - inset.right - inset.left, bounds.size.height - inset.top - inset.bottom)];
|
||||
[_textField setFrame:CGRectMake(inset.left, inset.top, bounds.size.width - inset.right - inset.left, bounds.size.height - inset.top - inset.bottom)];
|
||||
[_textField setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
|
||||
[_textField setFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
[_textField setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
|
||||
@@ -108,7 +108,7 @@
|
||||
return [_textField text];
|
||||
}
|
||||
|
||||
- (void)textField
|
||||
- (CPTextField)textField
|
||||
{
|
||||
return _textField;
|
||||
}
|
||||
@@ -417,8 +417,7 @@ var CPTableHeaderViewResizeZone = 3.0,
|
||||
}
|
||||
else if (_isDragging)
|
||||
{
|
||||
// Disable autoscrolling until it behaves correctly.
|
||||
//[self _autoscroll:theEvent localLocation:currentLocation];
|
||||
[self _autoscroll:theEvent localLocation:currentLocation];
|
||||
[self _dragTableColumn:_activeColumn to:currentLocation];
|
||||
}
|
||||
else // tracking a press, could become a drag
|
||||
@@ -462,18 +461,38 @@ var CPTableHeaderViewResizeZone = 3.0,
|
||||
@end
|
||||
|
||||
@implementation CPTableHeaderView (CPTrackingArea)
|
||||
{
|
||||
CPMutableArray _tableHeaderViewTrackingAreas;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[self removeAllTrackingAreas];
|
||||
|
||||
if (_tableHeaderViewTrackingAreas)
|
||||
{
|
||||
for (var i = 0, count = [_tableHeaderViewTrackingAreas count]; i < count; i++)
|
||||
[self removeTrackingArea:_tableHeaderViewTrackingAreas[i]];
|
||||
|
||||
_tableHeaderViewTrackingAreas = nil;
|
||||
}
|
||||
|
||||
var options = CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow;
|
||||
|
||||
|
||||
if (!_tableView)
|
||||
return;
|
||||
|
||||
_tableHeaderViewTrackingAreas = @[];
|
||||
|
||||
for (var i = 0; i < _tableView._tableColumns.length; i++)
|
||||
[self addTrackingArea:[[CPTrackingArea alloc] initWithRect:[self _cursorRectForColumn:i]
|
||||
options:options
|
||||
owner:self
|
||||
userInfo:nil]];
|
||||
{
|
||||
[_tableHeaderViewTrackingAreas addObject:[[CPTrackingArea alloc] initWithRect:[self _cursorRectForColumn:i]
|
||||
options:options
|
||||
owner:self
|
||||
userInfo:nil]];
|
||||
|
||||
[self addTrackingArea:_tableHeaderViewTrackingAreas[i]];
|
||||
}
|
||||
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
@@ -531,7 +550,7 @@ var CPTableHeaderViewResizeZone = 3.0,
|
||||
- (void)_autoscroll:(CPEvent)theEvent localLocation:(CGPoint)theLocation
|
||||
{
|
||||
// Constrain the y coordinate so we don't autoscroll vertically
|
||||
var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMinY([_tableView visibleRect])),
|
||||
var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMaxY([self frame])),
|
||||
constrainedEvent = [CPEvent mouseEventWithType:CPLeftMouseDragged
|
||||
location:[self convertPoint:constrainedLocation toView:nil]
|
||||
modifierFlags:[theEvent modifierFlags]
|
||||
@@ -679,20 +698,21 @@ var CPTableHeaderViewResizeZone = 3.0,
|
||||
[_columnDragClipView removeFromSuperview];
|
||||
[_tableView _setDraggedColumn:-1];
|
||||
|
||||
var headerView = [[[_tableView tableColumns] objectAtIndex:aColumnIndex] headerView];
|
||||
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
|
||||
headerView = [tableColumn headerView];
|
||||
|
||||
[[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
|
||||
|
||||
if (_tableView._draggedColumnIsSelected)
|
||||
[headerView setThemeState:CPThemeStateSelected];
|
||||
|
||||
var columnRect = [_tableView rectOfColumn:aColumnIndex];
|
||||
|
||||
[_tableView _reloadDataViews];
|
||||
[[_tableView headerView] setNeedsLayout];
|
||||
|
||||
[[CPCursor arrowCursor] set];
|
||||
[self updateTrackingAreas];
|
||||
|
||||
[_tableView _sendDelegateDidDragTableColumn:tableColumn];
|
||||
}
|
||||
|
||||
- (BOOL)_shouldResizeTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
|
||||
|
||||
+52
-45
@@ -203,7 +203,7 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
|
||||
@ingroup appkit
|
||||
@class CPTableView
|
||||
|
||||
CPTableView object displays record-oriented data in a table and
|
||||
A table view control displays record-oriented data in a table and
|
||||
allows the user to edit values and resize and rearrange columns.
|
||||
A CPTableView requires you to either set a data source which implements
|
||||
@ref numberofrows "numberOfRowsInTableView:" and @ref objectValueForTable "tableView:objectValueForTableColumn:row:",
|
||||
@@ -346,8 +346,8 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"alternating-row-colors": [CPNull null],
|
||||
@"grid-color": [CPNull null],
|
||||
@"alternating-row-colors": @[[CPColor whiteColor], [CPColor colorWithRed:245.0 / 255.0 green:249.0 / 255.0 blue:252.0 / 255.0 alpha:1.0]],
|
||||
@"grid-color": [CPColor colorWithHexString:@"dce0e2"],
|
||||
@"grid-line-thickness": 1.0,
|
||||
@"highlighted-grid-color": [CPNull null],
|
||||
@"selection-color": [CPNull null],
|
||||
@@ -368,7 +368,8 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
|
||||
@"dropview-above-border-color": [CPNull null],
|
||||
@"dropview-above-border-width": [CPNull null],
|
||||
@"dropview-above-selected-border-color": [CPNull null],
|
||||
@"dropview-above-selected-border-width": [CPNull null]
|
||||
@"dropview-above-selected-border-width": [CPNull null],
|
||||
@"header-view-height": 25.0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -390,8 +391,7 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
|
||||
_selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
|
||||
|
||||
[self setUsesAlternatingRowBackgroundColors:NO];
|
||||
[self setAlternatingRowBackgroundColors:
|
||||
[[CPColor whiteColor], [CPColor colorWithRed:245.0 / 255.0 green:249.0 / 255.0 blue:252.0 / 255.0 alpha:1.0]]];
|
||||
[self setAlternatingRowBackgroundColors:[self valueForThemeAttribute:@"alternating-row-colors"]];
|
||||
|
||||
_tableColumns = [];
|
||||
_tableColumnRanges = [];
|
||||
@@ -401,10 +401,10 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
|
||||
_intercellSpacing = CGSizeMake(3.0, 2.0);
|
||||
_rowHeight = [self valueForThemeAttribute:@"default-row-height"];
|
||||
|
||||
[self setGridColor:[CPColor colorWithHexString:@"dce0e2"]];
|
||||
[self setGridColor:[self valueForThemeAttribute:@"grid-color"]];
|
||||
[self setGridStyleMask:CPTableViewGridNone];
|
||||
|
||||
[self setHeaderView:[[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, _rowHeight)]];
|
||||
[self setHeaderView:[[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, [self valueForThemeAttribute:@"header-view-height"])]];
|
||||
[self setCornerView:[[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))]];
|
||||
|
||||
_currentHighlightedTableColumn = nil;
|
||||
@@ -2977,7 +2977,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
|
||||
|
||||
// Remove every main descriptor equivalents (normally only one)
|
||||
while ((descriptor = [e nextObject]) !== nil)
|
||||
while ((descriptor = [e nextObject]) != nil)
|
||||
{
|
||||
if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
|
||||
[outdatedDescriptors addObject:descriptor];
|
||||
@@ -3096,10 +3096,15 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
*/
|
||||
- (CPView)dragViewForRowsWithIndexes:(CPIndexSet)theDraggedRows tableColumns:(CPArray)theTableColumns event:(CPEvent)theDragEvent offset:(CGPoint)dragViewOffset
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
var firstRowFrame = [self frameOfDataViewAtColumn:0 row:[theDraggedRows firstIndex]],
|
||||
lastRowFrame = [self frameOfDataViewAtColumn:0 row:[theDraggedRows lastIndex]],
|
||||
dragViewHeight = CGRectGetMaxY(lastRowFrame) - CGRectGetMinY(firstRowFrame);
|
||||
|
||||
var bounds = CGRectMake(0, 0, CGRectGetWidth([self bounds]), dragViewHeight),
|
||||
dragView = [[CPView alloc] initWithFrame:bounds];
|
||||
|
||||
[dragView setAlphaValue:0.7];
|
||||
// [dragView setBackgroundColor:[CPColor colorWithWhite:1 alpha:0.5]];
|
||||
|
||||
// We have to fetch all the data views for the selected rows and columns
|
||||
// After that we can copy these add them to a transparent drag view and use that drag view
|
||||
@@ -3107,7 +3112,11 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
[self _enumerateViewsInRows:theDraggedRows tableColumns:theTableColumns usingBlock:function(v, row, tableColumn, stop)
|
||||
{
|
||||
var column = [_tableColumns indexOfObjectIdenticalTo:tableColumn],
|
||||
newDataView = [self preparedViewAtColumn:column row:row];
|
||||
newDataView = [self preparedViewAtColumn:column row:row],
|
||||
origin = [newDataView frame].origin;
|
||||
|
||||
origin.y -= CGRectGetMinY(firstRowFrame);
|
||||
[newDataView setFrameOrigin:origin];
|
||||
|
||||
[dragView addSubview:newDataView];
|
||||
}];
|
||||
@@ -3145,7 +3154,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
// Now a view that clips the column data views, which itself is clipped to the content view
|
||||
var columnVisRect = CGRectIntersection(columnRect, visibleRect);
|
||||
|
||||
frame = CGRectMake(0.0, CGRectGetHeight(headerFrame), CGRectGetWidth(columnVisRect), CGRectGetHeight(columnVisRect));
|
||||
frame = CGRectMake(0.0, CGRectGetHeight(headerFrame), CGRectGetWidth(columnRect), CGRectGetHeight(columnVisRect));
|
||||
|
||||
var columnClipView = [[CPView alloc] initWithFrame:frame];
|
||||
|
||||
@@ -3180,6 +3189,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
|
||||
// While dragging, the column is deselected in the table view
|
||||
[_selectedColumnIndexes removeIndex:columnIndex];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
return dragView;
|
||||
}
|
||||
@@ -3309,7 +3319,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
var oldSortDescriptors = [[self sortDescriptors] copy],
|
||||
newSortDescriptors = [CPArray array];
|
||||
|
||||
if (sortDescriptors !== nil)
|
||||
if (sortDescriptors != nil)
|
||||
[newSortDescriptors addObjectsFromArray:sortDescriptors];
|
||||
|
||||
if ([newSortDescriptors isEqual:oldSortDescriptors])
|
||||
@@ -3370,7 +3380,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
objectValue = tableColumnObjectValues[aRowIndex];
|
||||
|
||||
// tableView:objectValueForTableColumn:row: is optional if content bindings are in place.
|
||||
if (objectValue === undefined)
|
||||
if (objectValue == nil)
|
||||
{
|
||||
if ([self _dataSourceRespondsToObjectValueForTableColumn])
|
||||
{
|
||||
@@ -3751,7 +3761,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
{
|
||||
if (!dataViewsForRow)
|
||||
{
|
||||
dataViewsForRow = {}
|
||||
dataViewsForRow = {};
|
||||
_dataViewsForRows[row] = dataViewsForRow;
|
||||
}
|
||||
|
||||
@@ -3825,7 +3835,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
else if ([self _delegateRespondsToDataViewForTableColumn])
|
||||
_viewForTableColumnRowSelector = @selector(_sendDelegateDataViewForTableColumn:row:);
|
||||
|
||||
_isViewBased = (_viewForTableColumnRowSelector !== nil || _archivedDataViews !== nil);
|
||||
_isViewBased = (_viewForTableColumnRowSelector != nil || _archivedDataViews != nil);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4600,7 +4610,9 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
}
|
||||
|
||||
var bounds = [view bounds],
|
||||
viewLocation = CGPointMake(aPoint.x - CGRectGetWidth(bounds) / 2 + offset.x, aPoint.y - CGRectGetHeight(bounds) / 2 + offset.y);
|
||||
firstRowFrame = [self frameOfDataViewAtColumn:0 row:[_draggedRowIndexes firstIndex]],
|
||||
viewLocation = CGPointMake(aPoint.x - CGRectGetWidth(bounds) / 2 + offset.x, aPoint.y - CGRectGetHeight(bounds) / 2 + offset.y + CGRectGetMinY(firstRowFrame));
|
||||
|
||||
[self dragView:view at:viewLocation offset:CGPointMakeZero() event:[CPApp currentEvent] pasteboard:pboard source:self slideBack:YES];
|
||||
_startTrackingPoint = nil;
|
||||
|
||||
@@ -4712,7 +4724,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
dropOperation = [self _proposedDropOperationAtPoint:location],
|
||||
row = [self _proposedRowAtPoint:location];
|
||||
|
||||
if (_retargetedDropRow !== nil)
|
||||
if (_retargetedDropRow != nil)
|
||||
row = _retargetedDropRow;
|
||||
|
||||
var draggedTypes = [self registeredDraggedTypes],
|
||||
@@ -4768,7 +4780,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
*/
|
||||
- (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint
|
||||
{
|
||||
if (_retargetedDropOperation !== nil)
|
||||
if (_retargetedDropOperation != nil)
|
||||
return _retargetedDropOperation;
|
||||
|
||||
var row = [self _proposedRowAtPoint:theDragPoint],
|
||||
@@ -4847,10 +4859,10 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
row = [self _proposedRowAtPoint:location],
|
||||
dragOperation = [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
|
||||
|
||||
if (_retargetedDropRow !== nil)
|
||||
if (_retargetedDropRow != nil)
|
||||
row = _retargetedDropRow;
|
||||
|
||||
if (_retargetedDropOperation !== nil)
|
||||
if (_retargetedDropOperation != nil)
|
||||
dropOperation = _retargetedDropOperation;
|
||||
|
||||
if (dropOperation === CPTableViewDropOn && row >= numberOfRows)
|
||||
@@ -4895,8 +4907,8 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
operation = [self _proposedDropOperationAtPoint:location],
|
||||
row = _retargetedDropRow;
|
||||
|
||||
if (row === nil)
|
||||
var row = [self _proposedRowAtPoint:location];
|
||||
if (row == nil)
|
||||
row = [self _proposedRowAtPoint:location];
|
||||
|
||||
return [self _sendDataSourceAcceptDrop:sender row:row dropOperation:operation];
|
||||
}
|
||||
@@ -5097,14 +5109,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
}
|
||||
}
|
||||
else if (!_isViewBased && [aView isKindOfClass:[CPControl class]] && ![aView isKindOfClass:[CPTextField class]])
|
||||
{
|
||||
[self getColumn:@ref(column) row:@ref(row) forView:aView];
|
||||
|
||||
_editingColumn = column;
|
||||
_editingRow = row;
|
||||
|
||||
[aView addObserver:self forKeyPath:@"objectValue" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:"editing"];
|
||||
}
|
||||
|
||||
return aView;
|
||||
}
|
||||
@@ -5135,7 +5140,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
{
|
||||
[super viewWillMoveToWindow:aWindow];
|
||||
|
||||
[self _stopObservingFirstResponderForWindow:aWindow];
|
||||
[self _stopObservingFirstResponderForWindow:[self window]];
|
||||
|
||||
if (aWindow)
|
||||
[self _startObservingFirstResponderForWindow:aWindow];
|
||||
@@ -5236,7 +5241,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
if (!_isViewBased)
|
||||
{
|
||||
[self _setEditingState:NO forView:textField];
|
||||
[self _commitDataViewObjectValue:textField];
|
||||
[self _commitDataViewObjectValue:textField forColumn:_editingColumn andRow:_editingRow];
|
||||
}
|
||||
else
|
||||
[textField setBezeled:NO];
|
||||
@@ -5286,19 +5291,19 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
The action for any dataview that supports editing. This will only be called when the value was changed.
|
||||
The table view becomes the first responder after user is done editing a dataview.
|
||||
*/
|
||||
- (void)_commitDataViewObjectValue:(id)aDataView
|
||||
- (void)_commitDataViewObjectValue:(id)aDataView forColumn:(CPInteger)column andRow:(CPInteger)row
|
||||
{
|
||||
var editingTableColumn = _tableColumns[_editingColumn];
|
||||
var editingTableColumn = _tableColumns[column];
|
||||
|
||||
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
|
||||
[_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:_editingRow];
|
||||
[_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:row];
|
||||
|
||||
// Allow the column binding to do a reverse set. Note that we do this even if the data source method above
|
||||
// is implemented.
|
||||
[editingTableColumn _reverseSetDataView:aDataView forRow:_editingRow];
|
||||
[editingTableColumn _reverseSetDataView:aDataView forRow:row];
|
||||
|
||||
if (_editingRow !== CPNotFound && _editingColumn !== CPNotFound)
|
||||
[self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:_editingRow] columnIndexes:[CPIndexSet indexSetWithIndex:_editingColumn]];
|
||||
if (row !== CPNotFound && column !== CPNotFound)
|
||||
[self _reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:row] columnIndexes:[CPIndexSet indexSetWithIndex:column]];
|
||||
}
|
||||
|
||||
- (void)_setEditingState:(BOOL)editingState forView:(CPView)aView
|
||||
@@ -5348,9 +5353,12 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
if (context === "editing" && [object superview] === self)
|
||||
{
|
||||
[object removeObserver:self forKeyPath:keyPath];
|
||||
[self _commitDataViewObjectValue:object];
|
||||
_editingRow = CPNotFound;
|
||||
_editingColumn = CPNotFound;
|
||||
|
||||
var row,
|
||||
column;
|
||||
|
||||
[self getColumn:@ref(column) row:@ref(row) forView:object];
|
||||
[self _commitDataViewObjectValue:object forColumn:column andRow:row];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5752,10 +5760,10 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
@ignore
|
||||
Call the delegate didDragTableColumn with the given tableColumn
|
||||
*/
|
||||
- (void)_sendDelegateDidDragTableColumn:(CPInteger)column
|
||||
- (void)_sendDelegateDidDragTableColumn:(CPTableColumn)aTableColumn
|
||||
{
|
||||
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didDragTableColumn_)
|
||||
[_delegate tableView:self didDragTableColumn:_tableColumns[column]];
|
||||
[_delegate tableView:self didDragTableColumn:aTableColumn];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -6111,7 +6119,6 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
|
||||
CPTableViewGridStyleMaskKey = @"CPTableViewGridStyleMaskKey",
|
||||
CPTableViewUsesAlternatingBackgroundKey = @"CPTableViewUsesAlternatingBackgroundKey",
|
||||
CPTableViewAlternatingRowColorsKey = @"CPTableViewAlternatingRowColorsKey",
|
||||
CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
|
||||
CPTableViewCornerViewKey = @"CPTableViewCornerViewKey",
|
||||
CPTableViewAutosaveNameKey = @"CPTableViewAutosaveNameKey",
|
||||
CPTableViewArchivedReusableViewsKey = @"CPTableViewArchivedReusableViewsKey";
|
||||
@@ -6362,7 +6369,7 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
|
||||
|
||||
if (tableView._draggedColumnIsSelected)
|
||||
{
|
||||
CGContextSetFillColor(context, [tableView selectionHighlightColor]);
|
||||
CGContextSetFillColor(context, [tableView _isFocused] ? [tableView selectionHighlightColor] : [tableView unfocusedSelectionHighlightColor]);
|
||||
CGContextFillRect(context, bounds);
|
||||
}
|
||||
else
|
||||
|
||||
+329
-5
@@ -5,6 +5,13 @@
|
||||
* Created by Alexander Ljungberg.
|
||||
* Copyright 2010, WireLoad, LLC.
|
||||
*
|
||||
* additions from
|
||||
*
|
||||
* Daniel Boehringer on 8/02/2014.
|
||||
* Copyright Daniel Boehringer on 8/02/2014.
|
||||
*
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
@@ -20,6 +27,30 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@global CPStringPboardType
|
||||
@global _CPSmartPboardType
|
||||
|
||||
@class CPAttributedString
|
||||
@class _CPRTFParser
|
||||
|
||||
@protocol CPTextDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (BOOL)textShouldBeginEditing:(CPText)aTextObject;
|
||||
- (BOOL)textShouldEndEditing:(CPText)aTextObject;
|
||||
- (void)textDidBeginEditing:(CPNotification)aNotification;
|
||||
- (void)textDidChange:(CPNotification)aNotification;
|
||||
- (void)textDidEndEditing:(CPNotification)aNotification;
|
||||
|
||||
@end
|
||||
|
||||
CPParagraphSeparatorCharacter = 0x2029;
|
||||
CPLineSeparatorCharacter = 0x2028;
|
||||
CPEnterCharacter = "\u0003";
|
||||
CPBackspaceCharacter = "\u0008";
|
||||
CPTabCharacter = "\u0009";
|
||||
@@ -29,6 +60,7 @@ CPCarriageReturnCharacter = "\u000d";
|
||||
CPBackTabCharacter = "\u0019";
|
||||
CPDeleteCharacter = "\u007f";
|
||||
|
||||
@typedef CPTextMovement
|
||||
CPIllegalTextMovement = 0;
|
||||
CPOtherTextMovement = 0;
|
||||
CPReturnTextMovement = 16;
|
||||
@@ -46,8 +78,300 @@ CPWritingDirectionLeftToRight = 0;
|
||||
CPWritingDirectionRightToLeft = 1;
|
||||
|
||||
@typedef CPTextAlignment
|
||||
CPLeftTextAlignment = 0;
|
||||
CPRightTextAlignment = 1;
|
||||
CPCenterTextAlignment = 2;
|
||||
CPJustifiedTextAlignment = 3;
|
||||
CPNaturalTextAlignment = 4;
|
||||
CPLeftTextAlignment = 0;
|
||||
CPRightTextAlignment = 1;
|
||||
CPCenterTextAlignment = 2;
|
||||
CPJustifiedTextAlignment = 3;
|
||||
CPNaturalTextAlignment = 4;
|
||||
|
||||
@typedef CPUnderlineStyle
|
||||
CPUnderlineStyleNone = 0;
|
||||
CPUnderlineStyleSingle = 1;
|
||||
CPUnderlineStyleThick = 2;
|
||||
CPUnderlineStyleDouble = 3;
|
||||
CPUnderlineStylePatternSolid = 4;
|
||||
CPUnderlineStylePatternDot = 5;
|
||||
CPUnderlineStylePatternDash = 6;
|
||||
CPUnderlineStylePatternDashDot = 7;
|
||||
CPUnderlineStylePatternDashDotDot = 8;
|
||||
CPUnderlineStyleByWord = 9;
|
||||
|
||||
/*
|
||||
CPText notifications
|
||||
*/
|
||||
CPTextDidBeginEditingNotification = @"CPTextDidBeginEditingNotification";
|
||||
CPTextDidChangeNotification = @"CPTextDidChangeNotification";
|
||||
CPTextDidEndEditingNotification = @"CPTextDidEndEditingNotification";
|
||||
|
||||
/*
|
||||
CPTextView Notifications
|
||||
*/
|
||||
CPTextViewDidChangeSelectionNotification = @"CPTextViewDidChangeSelectionNotification";
|
||||
CPTextViewDidChangeTypingAttributesNotification = @"CPTextViewDidChangeTypingAttributesNotification";
|
||||
|
||||
/*
|
||||
FIXME: move these to CPAttributed string
|
||||
Make use of attributed keys in AppKit
|
||||
*/
|
||||
CPFontAttributeName = @"CPFontAttributeName";
|
||||
CPForegroundColorAttributeName = @"CPForegroundColorAttributeName";
|
||||
CPBackgroundColorAttributeName = @"CPBackgroundColorAttributeName";
|
||||
CPShadowAttributeName = @"CPShadowAttributeName";
|
||||
CPUnderlineStyleAttributeName = @"CPUnderlineStyleAttributeName";
|
||||
CPSuperscriptAttributeName = @"CPSuperscriptAttributeName";
|
||||
CPBaselineOffsetAttributeName = @"CPBaselineOffsetAttributeName";
|
||||
CPAttachmentAttributeName = @"CPAttachmentAttributeName";
|
||||
CPLigatureAttributeName = @"CPLigatureAttributeName";
|
||||
CPKernAttributeName = @"CPKernAttributeName";
|
||||
|
||||
@implementation CPText : CPView
|
||||
{
|
||||
BOOL _isEditable @accessors(getter=isEditable, setter=setEditable:);
|
||||
BOOL _isSelectable @accessors(getter=isSelectable, setter=setSelectable:);
|
||||
BOOL _isRichText @accessors(getter=isRichText, setter=setRichText:);
|
||||
}
|
||||
|
||||
- (void)setSelectable:(BOOL)flag
|
||||
{
|
||||
[self willChangeValueForKey:@"selectable"];
|
||||
_isSelectable = flag;
|
||||
[self didChangeValueForKey:@"selectable"];
|
||||
|
||||
if (!flag)
|
||||
[self setEditable:flag];
|
||||
}
|
||||
|
||||
- (void)setEditable:(BOOL)flag
|
||||
{
|
||||
[self willChangeValueForKey:@"editable"];
|
||||
_isEditable = flag;
|
||||
[self didChangeValueForKey:@"editable"];
|
||||
|
||||
if (flag)
|
||||
[self setSelectable:flag];
|
||||
}
|
||||
|
||||
- (void)changeFont:(id)sender
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)copy:(id)sender
|
||||
{
|
||||
var selectedRange = [self selectedRange];
|
||||
|
||||
if (selectedRange.length < 1)
|
||||
return;
|
||||
|
||||
var pasteboard = [CPPasteboard generalPasteboard];
|
||||
|
||||
// put plain representation on the pasteboad unconditionally
|
||||
[pasteboard declareTypes:[CPStringPboardType] owner:nil];
|
||||
[pasteboard setString:[[self stringValue] substringWithRange:selectedRange] forType:CPStringPboardType];
|
||||
}
|
||||
|
||||
- (id)_plainStringForPasting
|
||||
{
|
||||
return [[CPPasteboard generalPasteboard] stringForType:CPStringPboardType];
|
||||
}
|
||||
|
||||
- (id)_stringForPasting
|
||||
{
|
||||
var pasteboard = [CPPasteboard generalPasteboard],
|
||||
dataForPasting = [pasteboard stringForType:CPRTFPboardType],
|
||||
stringForPasting = [pasteboard stringForType:CPStringPboardType],
|
||||
attributedStringData = [pasteboard stringForType:_CPASPboardType];
|
||||
|
||||
if ([self isRichText] && attributedStringData)
|
||||
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:attributedStringData]];
|
||||
|
||||
if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"])
|
||||
stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting];
|
||||
|
||||
if (![self isRichText] && [stringForPasting isKindOfClass:[CPAttributedString class]])
|
||||
stringForPasting = stringForPasting._string;
|
||||
|
||||
return stringForPasting;
|
||||
}
|
||||
|
||||
- (BOOL)_shouldUseSmartPasting
|
||||
{
|
||||
return parseInt([[CPPasteboard generalPasteboard] stringForType:_CPSmartPboardType], 10) > 0 || NO;
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender
|
||||
{
|
||||
var stringForPasting = [self _stringForPasting];
|
||||
|
||||
if (stringForPasting)
|
||||
[self insertText:stringForPasting];
|
||||
}
|
||||
|
||||
- (void)copyFont:(id)sender
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)delete:(id)sender
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (CPFont)font:(CPFont)aFont
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isHorizontallyResizable
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isRulerVisible
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isVerticallyResizable
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CGSize)maxSize
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return CGSizeMake(0,0);
|
||||
}
|
||||
|
||||
- (CGSize)minSize
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
return CGSizeMake(0,0);
|
||||
}
|
||||
|
||||
- (void)pasteFont:(id)sender
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)scrollRangeToVisible:(CPRange)aRange
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)selectedAll:(id)sender
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (CPRange)selectedRange
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return CPMakeRange(CPNotFound, 0);
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont range:(CPRange)aRange
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setHorizontallyResizable:(BOOL)flag
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setMaxSize:(CGSize)aSize
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setMinSize:(CGSize)aSize
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setString:(CPString)aString
|
||||
{
|
||||
[self replaceCharactersInRange:CPMakeRange(0, [[self string] length]) withString:aString];
|
||||
}
|
||||
|
||||
- (void)setUsesFontPanel:(BOOL)flag
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setVerticallyResizable:(BOOL)flag
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (CPString)string
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)underline:(id)sender
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
- (BOOL)usesFontPanel
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTextViewIsEditableKey = @"CPTextViewIsEditableKey",
|
||||
CPTextViewIsSelectableKey = @"CPTextViewIsSelectableKey",
|
||||
CPTextViewIsRichTextKey = @"CPTextViewIsRichTextKey";
|
||||
|
||||
@implementation CPText (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setSelectable:[aCoder decodeBoolForKey:CPTextViewIsSelectableKey]];
|
||||
[self setEditable:[aCoder decodeBoolForKey:CPTextViewIsEditableKey]];
|
||||
[self setRichText:[aCoder decodeBoolForKey:CPTextViewIsRichTextKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
[aCoder encodeBool:_isEditable forKey:CPTextViewIsEditableKey];
|
||||
[aCoder encodeBool:_isSelectable forKey:CPTextViewIsSelectableKey];
|
||||
[aCoder encodeBool:_isRichText forKey:CPTextViewIsRichTextKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+277
-86
@@ -34,6 +34,8 @@
|
||||
@global CPStringPboardType
|
||||
@global CPCursor
|
||||
|
||||
@global document
|
||||
|
||||
@protocol CPTextFieldDelegate <CPControlTextEditingDelegate>
|
||||
|
||||
@end
|
||||
@@ -77,26 +79,16 @@ function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resig
|
||||
|
||||
var ownerWindow = [owner window];
|
||||
|
||||
if (!resigning && [ownerWindow isKeyWindow])
|
||||
if (!resigning && [ownerWindow isKeyWindow] && [ownerWindow firstResponder] === owner)
|
||||
{
|
||||
/*
|
||||
Browsers blur text fields when a click occurs anywhere outside the text field. That is normal for browsers, but in Cocoa the key view retains focus unless the click target accepts first responder. So if we lost focus but were not told to resign and our window is still key, restore focus,
|
||||
but only if the text field is completely within the browser window. If we restore focus when it
|
||||
is off screen, the entire body scrolls out of our control.
|
||||
Previously we had code here which would force the input to regain focus if the input lost focus without the CPTextField having actually lost first responder status. This typically happened because the user clicked away from the input in the browser, without clicking on something that could become the first responder. In the browser, clicking outside of an input blurs it, but in Cocoa and Cappuccino it does not (unless you actually click something that will become the first responder).
|
||||
|
||||
That refocusing code has now been removed because we simply prevent the default action on clicks in the browser instead, which combined with the fix in 58d5d7d7, successfully prevents unintentional focus loss in (at least) Safari 9.1.1, Chrome 51 and Safari for iOS 9.3 when you click outside of a text field.
|
||||
|
||||
Now we can still lose focus unexpectedly: this is when the 'done' button is tapped on the virtual keyboard of a mobile device. In this case we actually do want to resign first responder status, because that is what the done button should do (and if we did not the keyboard would go away and then immediately come back which looks dumb and isn't what the user wanted).
|
||||
*/
|
||||
if ([owner _isWithinUsablePlatformRect])
|
||||
{
|
||||
[[CPRunLoop mainRunLoop] performBlock:function()
|
||||
{
|
||||
// This will prevent to jump to the focused element
|
||||
var previousScrollingOrigin = [owner _scrollToVisibleRectAndReturnPreviousOrigin];
|
||||
|
||||
inputElement.focus();
|
||||
|
||||
[owner _restorePreviousScrollingOrigin:previousScrollingOrigin];
|
||||
}
|
||||
argument:nil order:0 modes:[CPDefaultRunLoopMode]];
|
||||
}
|
||||
[ownerWindow makeFirstResponder:nil];
|
||||
}
|
||||
|
||||
CPTextFieldHandleBlur(anEvent, @ref(owner));
|
||||
@@ -179,7 +171,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
+ (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
|
||||
{
|
||||
var textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
|
||||
var minSize = aTheme ? [aTheme valueForAttributeWithName:@"min-size" forClass:CPTextField] : CGSizeMake(0,0),
|
||||
textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, minSize.height)];
|
||||
|
||||
[textField setTheme:aTheme];
|
||||
[textField setStringValue:aStringValue];
|
||||
@@ -200,7 +193,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
+ (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
|
||||
{
|
||||
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
|
||||
var minSize = aTheme ? [aTheme valueForAttributeWithName:@"min-size" forClass:CPTextField] : CGSizeMake(0,0),
|
||||
textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, minSize.height)];
|
||||
|
||||
[textField setTheme:aTheme];
|
||||
[textField setStringValue:aStringValue];
|
||||
@@ -241,6 +235,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
@"bezel-inset": CGInsetMakeZero(),
|
||||
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
|
||||
@"bezel-color": [CPNull null],
|
||||
@"min-size": CGSizeMake(0, 29),
|
||||
@"background-inset": CGInsetMakeZero()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -341,7 +337,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[CPTextFieldInputOwner keyUp:cappEvent];
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
};
|
||||
|
||||
CPTextFieldDOMPasswordInputElement.oninput = CPTextFieldInputFunction;
|
||||
CPTextFieldDOMPasswordInputElement.onblur = CPTextFieldBlurHandler;
|
||||
@@ -375,8 +371,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self setPlaceholderString:@""];
|
||||
|
||||
_sendActionOn = CPKeyUpMask | CPKeyDownMask;
|
||||
|
||||
[self setValue:CPNaturalTextAlignment forThemeAttribute:@"alignment"];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -403,7 +397,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateEditable];
|
||||
|
||||
// We only allow first responder status if the field is enable, and editable or selectable.
|
||||
// We only allow first responder status if the field is enabled, and editable or selectable.
|
||||
if (!(shouldBeEditable && ![self isSelectable]) && [[self window] firstResponder] === self)
|
||||
[[self window] makeFirstResponder:nil];
|
||||
|
||||
@@ -411,6 +405,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self setThemeState:CPThemeStateEditable];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateEditable];
|
||||
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -432,6 +428,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
// We only allow first responder status if the field is enabled.
|
||||
if (!shouldBeEnabled && [[self window] firstResponder] === self)
|
||||
[[self window] makeFirstResponder:nil];
|
||||
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -441,6 +439,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
- (void)setSelectable:(BOOL)aFlag
|
||||
{
|
||||
_isSelectable = aFlag;
|
||||
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -561,9 +561,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
Sets the background color, which is shown for non-bezeled text fields with drawsBackground set to YES
|
||||
@param aColor The background color
|
||||
*/
|
||||
- (void)setTextFieldBackgroundColor:(CPColor)aColor
|
||||
- (void)setBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
if (_textFieldBackgroundColor == aColor)
|
||||
if (_backgroundColor == aColor)
|
||||
return;
|
||||
|
||||
_textFieldBackgroundColor = aColor;
|
||||
@@ -575,7 +575,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
/*!
|
||||
Returns the background color.
|
||||
*/
|
||||
- (CPColor)textFieldBackgroundColor
|
||||
- (CPColor)backgroundColor
|
||||
{
|
||||
return _textFieldBackgroundColor;
|
||||
}
|
||||
@@ -690,8 +690,38 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
}
|
||||
else
|
||||
{
|
||||
var point = CGPointMake([self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x - [self currentValueForThemeAttribute:@"content-inset"].left, 0),
|
||||
position = [CPPlatformString charPositionOfString:[self stringValue] withFont:[self font] forPoint:point];
|
||||
var x = [self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x,
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
text = [self stringValue],
|
||||
font = [self font];
|
||||
|
||||
switch ([self alignment]) {
|
||||
case CPCenterTextAlignment:
|
||||
|
||||
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
|
||||
textWidth = [text sizeWithFont:font].width;
|
||||
|
||||
x -= (contentWidth - textWidth) / 2 + contentInset.left;
|
||||
|
||||
break;
|
||||
|
||||
case CPRightTextAlignment:
|
||||
|
||||
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
|
||||
textWidth = [text sizeWithFont:font].width;
|
||||
|
||||
x -= (contentWidth - textWidth) + contentInset.left;
|
||||
|
||||
break;
|
||||
|
||||
default: // CPLeftTextAlignment, CPJustifiedTextAlignment, CPNaturalTextAlignment
|
||||
|
||||
x -= contentInset.left;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var position = [CPPlatformString charPositionOfString:text withFont:font forPoint:CGPointMake(x, 0)];
|
||||
|
||||
[self setSelectedRange:CPMakeRange(position, 0)];
|
||||
}
|
||||
@@ -730,7 +760,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var element = [self _inputElement],
|
||||
font = [self currentValueForThemeAttribute:@"font"],
|
||||
font = [self font],
|
||||
lineHeight = [font defaultLineHeightForFont],
|
||||
contentRect = [self contentRectForBounds:[self bounds]],
|
||||
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"],
|
||||
@@ -787,7 +817,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
element.style.zIndex = 1000;
|
||||
element.style.top = topPoint;
|
||||
element.style.lineHeight = ROUND(lineHeight) + "px";
|
||||
element.style.height = isTextArea ? CGRectGetHeight(contentRect) + "px" : ROUND(lineHeight) + "px";;
|
||||
element.style.height = isTextArea ? CGRectGetHeight(contentRect) + "px" : ROUND(lineHeight) + "px";
|
||||
element.style.width = CGRectGetWidth(contentRect) + "px";
|
||||
element.style.left = left + "px";
|
||||
element.style.verticalAlign = "top";
|
||||
@@ -839,7 +869,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self _resignFirstKeyResponder];
|
||||
|
||||
_isEditing = NO;
|
||||
|
||||
|
||||
if ([self isEditable])
|
||||
{
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:@{"CPTextMovement": [self _currentTextMovement]}]];
|
||||
@@ -877,12 +907,18 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
if (!CPTextFieldInputDidBlur)
|
||||
CPTextFieldBlurHandler();
|
||||
|
||||
CPTextFieldInputDidBlur = NO;
|
||||
CPTextFieldInputResigning = NO;
|
||||
|
||||
if (element.parentNode == _DOMElement)
|
||||
element.parentNode.removeChild(element);
|
||||
|
||||
// Previosly, we unflagged CPTextFieldInputDidBlur and CPTextFieldInputResigning before
|
||||
// the call to removeChild. This resulted in DOM exceptions in Chrome under certain conditions.
|
||||
// See https://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node
|
||||
// for why we need to unflag CPTextFieldInputDidBlur and CPTextFieldInputResigning
|
||||
// only after removing the element.
|
||||
|
||||
CPTextFieldInputDidBlur = NO;
|
||||
CPTextFieldInputResigning = NO;
|
||||
|
||||
CPTextFieldInputIsActive = NO;
|
||||
|
||||
if (document.attachEvent)
|
||||
@@ -999,14 +1035,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(CPEvent)anEvent
|
||||
{
|
||||
[super mouseMoved:anEvent];
|
||||
[self _updateCursorForEvent:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return [[self nextResponder] mouseDown:anEvent];
|
||||
|
||||
// Don't track! (ever?)
|
||||
if ([self isEditable] && [self isEnabled])
|
||||
{
|
||||
@@ -1041,7 +1074,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[[self window] platformWindow]._DOMBodyElement.ondrag = CPTextFieldCachedDragFunction;
|
||||
[[self window] platformWindow]._DOMBodyElement.onselectstart = CPTextFieldCachedSelectStartFunction;
|
||||
|
||||
CPTextFieldCachedSelectStartFunction = nil
|
||||
CPTextFieldCachedSelectStartFunction = nil;
|
||||
CPTextFieldCachedDragFunction = nil;
|
||||
}
|
||||
|
||||
@@ -1093,7 +1126,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
if (!([self isEnabled] && [self isEditable]))
|
||||
// Has to be enabled, and it also has to be editable or selectable.
|
||||
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
|
||||
return;
|
||||
|
||||
// CPTextField uses an HTML input element to take the input so we need to
|
||||
@@ -1244,7 +1278,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
return;
|
||||
|
||||
if (_implementedDelegateMethods & CPTextFieldDelegate_controlTextDidBeginEditing_)
|
||||
[_delegate controlTextDidBeginEditing:[[CPNotification alloc] initWithName:CPControlTextDidBeginEditingNotification object:self userInfo:@{"CPFieldEditor": [note object]}]]
|
||||
[_delegate controlTextDidBeginEditing:[[CPNotification alloc] initWithName:CPControlTextDidBeginEditingNotification object:self userInfo:@{"CPFieldEditor": [note object]}]];
|
||||
|
||||
[super textDidBeginEditing:note];
|
||||
}
|
||||
@@ -1261,27 +1295,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[_delegate controlTextDidEndEditing:note];
|
||||
}
|
||||
|
||||
- (void)_updateCursorForEvent:(CPEvent)anEvent
|
||||
{
|
||||
var frame = CGRectMakeCopy([self frame]),
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
|
||||
frame = [[self superview] convertRectToBase:CGRectInsetByInset(frame, contentInset)];
|
||||
|
||||
if ([self isEnabled] && ([self isSelectable] || [self isEditable]) && CGRectContainsPoint(frame, [anEvent locationInWindow]))
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
self._DOMElement.style.cursor = "text";
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
self._DOMElement.style.cursor = "default";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string in the text field.
|
||||
*/
|
||||
@@ -1353,7 +1366,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
// If there is a formatter, make sure the object value can be formatted successfully
|
||||
var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue];
|
||||
|
||||
if (formattedString === nil)
|
||||
if (formattedString == nil)
|
||||
{
|
||||
var value = nil;
|
||||
|
||||
@@ -1363,7 +1376,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
value = undefined;
|
||||
|
||||
[super setObjectValue:value];
|
||||
_stringValue = (value === nil || value === undefined) ? @"" : String(value);
|
||||
_stringValue = (value == nil) ? @"" : String(value);
|
||||
}
|
||||
else
|
||||
_stringValue = formattedString;
|
||||
@@ -1450,7 +1463,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
lineBreakMode = [self lineBreakMode],
|
||||
text = (_stringValue || @" "),
|
||||
textSize = CGSizeMakeCopy(frameSize),
|
||||
font = [self currentValueForThemeAttribute:@"font"];
|
||||
font = [self font];
|
||||
|
||||
textSize.width -= contentInset.left + contentInset.right;
|
||||
textSize.height -= contentInset.top + contentInset.bottom;
|
||||
@@ -1459,7 +1472,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
![self isBezeled] &&
|
||||
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
|
||||
{
|
||||
textSize = [text sizeWithFont:font inWidth:textSize.width];
|
||||
textSize = [text sizeWithFont:font inWidth:textSize.width] + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1519,7 +1532,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
else
|
||||
[[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]];
|
||||
}
|
||||
else if (wind !== nil && [wind makeFirstResponder:self])
|
||||
else if (wind != nil && [wind makeFirstResponder:self])
|
||||
[self _selectText:sender immediately:immediately];
|
||||
}
|
||||
else
|
||||
@@ -1529,7 +1542,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
#else
|
||||
// Even if we can't actually select the text we need to preserve the first
|
||||
// responder side effect.
|
||||
if (wind !== nil && [wind firstResponder] !== self)
|
||||
if (wind != nil && [wind firstResponder] !== self)
|
||||
[wind makeFirstResponder:self];
|
||||
#endif
|
||||
}
|
||||
@@ -1812,7 +1825,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(control:didFailToFormatString:errorDescription:)])
|
||||
_implementedDelegateMethods |= CPTextFieldDelegate_control_didFailToFormatString_errorDescription_
|
||||
_implementedDelegateMethods |= CPTextFieldDelegate_control_didFailToFormatString_errorDescription_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(controlTextDidBeginEditing:)])
|
||||
_implementedDelegateMethods |= CPTextFieldDelegate_controlTextDidBeginEditing_;
|
||||
@@ -1854,6 +1867,12 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
if (aName === "bezel-view")
|
||||
return [self bezelRectForBounds:[self bounds]];
|
||||
|
||||
else if (aName === "background-view")
|
||||
{
|
||||
var backgroundInset = [self currentValueForThemeAttribute:@"background-inset"];
|
||||
|
||||
return CGRectInsetByInset([self bounds], backgroundInset);
|
||||
}
|
||||
else if (aName === "content-view")
|
||||
return [self contentRectForBounds:[self bounds]];
|
||||
|
||||
@@ -1870,6 +1889,14 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
return view;
|
||||
}
|
||||
else if (aName === "background-view")
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
else
|
||||
{
|
||||
var view = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
|
||||
@@ -1884,16 +1911,37 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
var bezelColor = [self currentValueForThemeAttribute:@"bezel-color"];
|
||||
|
||||
if ([bezelColor isCSSBased])
|
||||
{
|
||||
// CSS Styling
|
||||
// We don't need bezelView as we apply CSS styling directly on the text field view itself
|
||||
|
||||
if (bezelView)
|
||||
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
||||
// We need to call [super setBackgroundColor:] as we have redefined it here
|
||||
[super setBackgroundColor:bezelColor];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:nil],
|
||||
backgroundView = [self layoutEphemeralSubviewNamed:@"background-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
[backgroundView setBackgroundColor:(_drawsBackground ? _textFieldBackgroundColor : [CPColor clearColor])];
|
||||
}
|
||||
else
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
|
||||
[bezelView setBackgroundColor:bezelColor];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
}
|
||||
|
||||
if (contentView)
|
||||
{
|
||||
@@ -1914,7 +1962,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[contentView setText:string];
|
||||
|
||||
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
|
||||
[contentView setFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
[contentView setFont:[self font]];
|
||||
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
|
||||
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
|
||||
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
|
||||
@@ -1954,8 +2002,28 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
// We don't want to change the text-color of the placeHolder of the textField
|
||||
var placeholderColor = [self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder];
|
||||
|
||||
[super setTextColor:aColor];
|
||||
// If the text field is a cell based table data view, we need to fix the color for all possible states
|
||||
if ([self hasThemeState:CPThemeStateTableDataView])
|
||||
{
|
||||
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView]];
|
||||
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]];
|
||||
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView, CPThemeStateFirstResponder, CPThemeStateKeyWindow]];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([self hasThemeState:CPTextFieldStateRounded])
|
||||
{
|
||||
[self setTextColor:aColor inThemeStates:[CPTextFieldStateRounded]];
|
||||
[self setTextColor:aColor inThemeStates:[CPTextFieldStateRounded, CPThemeStateEditing]];
|
||||
}
|
||||
|
||||
[self setTextColor:aColor inThemeStates:[CPThemeStateNormal]];
|
||||
[self setTextColor:aColor inThemeStates:[CPThemeStateEditing]];
|
||||
}
|
||||
|
||||
[self setValue:placeholderColor forThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)viewDidHide
|
||||
@@ -2015,7 +2083,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
frame.origin = [wind convertBaseToGlobal:frame.origin];
|
||||
|
||||
// Here we restore the the previous scrolling posiition
|
||||
// Here we restore the previous scrolling posiition
|
||||
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
|
||||
|
||||
return (CGRectGetMinX(frame) >= CGRectGetMinX(usableRect) &&
|
||||
@@ -2097,8 +2165,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
|
||||
[self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
|
||||
|
||||
[self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
|
||||
|
||||
[self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
|
||||
[self setBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
|
||||
|
||||
[self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]];
|
||||
[self setAlignment:[aCoder decodeIntForKey:CPTextFieldAlignmentKey]];
|
||||
@@ -2108,6 +2175,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
|
||||
[self _setUsesSingleLineMode:[aCoder decodeBoolForKey:CPTextFieldUsesSingleLineMode]];
|
||||
[self _setWraps:[aCoder decodeBoolForKey:CPTextFieldWraps]];
|
||||
[self _setScrolls:[aCoder decodeBoolForKey:CPTextFieldScrolls]];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -2142,6 +2210,56 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
|
||||
|
||||
@implementation _CPTextFieldValueBinder : CPBinder
|
||||
|
||||
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
|
||||
{
|
||||
var theBinding = [self getBinding:aBinding forObject:anObject],
|
||||
notificationCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (theBinding)
|
||||
{
|
||||
[notificationCenter removeObserver:[theBinding._info objectForKey:CPObservedObjectKey]
|
||||
name:CPControlTextDidBeginEditingNotification
|
||||
object:anObject];
|
||||
|
||||
[notificationCenter removeObserver:[theBinding._info objectForKey:CPObservedObjectKey]
|
||||
name:CPControlTextDidEndEditingNotification
|
||||
object:anObject];
|
||||
|
||||
[super unbind:aBinding forObject:anObject];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
|
||||
{
|
||||
self = [super initWithBinding:aBinding
|
||||
name:aName
|
||||
to:aDestination
|
||||
keyPath:aKeyPath
|
||||
options:options
|
||||
from:aSource];
|
||||
|
||||
var notificationCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
// This gives us support for the CPEditorRegistration informal protocol
|
||||
if ([aDestination respondsToSelector:@selector(_objectDidBeginEditing:)])
|
||||
{
|
||||
[notificationCenter addObserver:aDestination
|
||||
selector:@selector(_objectDidBeginEditing:)
|
||||
name:CPControlTextDidBeginEditingNotification
|
||||
object:aSource];
|
||||
}
|
||||
|
||||
if ([aDestination respondsToSelector:@selector(_objectDidEndEditing:)])
|
||||
{
|
||||
[notificationCenter addObserver:aDestination
|
||||
selector:@selector(_objectDidEndEditing:)
|
||||
name:CPControlTextDidEndEditingNotification
|
||||
object:aSource];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
||||
{
|
||||
[super _updatePlaceholdersWithOptions:options];
|
||||
@@ -2174,7 +2292,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
|
||||
newValue = [self valueForBinding:aBinding],
|
||||
value = [destination valueForKeyPath:keyPath];
|
||||
|
||||
if (CPIsControllerMarker(value) && newValue === nil)
|
||||
if (CPIsControllerMarker(value) && newValue == nil)
|
||||
return;
|
||||
|
||||
newValue = [self reverseTransformValue:newValue withOptions:options];
|
||||
@@ -2202,4 +2320,77 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
|
||||
[_source setObjectValue:aValue];
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@implementation CPTextField (CPTrackingArea)
|
||||
{
|
||||
CPTrackingArea _textFieldTrackingArea;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (_textFieldTrackingArea)
|
||||
{
|
||||
[self removeTrackingArea:_textFieldTrackingArea];
|
||||
_textFieldTrackingArea = nil;
|
||||
}
|
||||
|
||||
if ([self isEnabled] && (_isEditable || _isSelectable))
|
||||
{
|
||||
var myBounds = CGRectMakeCopy([self bounds]),
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
|
||||
_textFieldTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectInsetByInset(myBounds, contentInset)
|
||||
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
|
||||
[self addTrackingArea:_textFieldTrackingArea];
|
||||
}
|
||||
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
[[CPCursor IBeamCursor] set];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPTextField (TableDataView)
|
||||
|
||||
// We overide here _CPObject+Theme setValue:forThemeAttribute as CPTextField can be used as tableView data view
|
||||
// So, when outside a table data view, setValue:forThemeAttribute should store the value with the CPThemeStateNormal (default behavior)
|
||||
// When inside a table data view, it should store the value with the CPThemeStateTableDataView. If not, the value won't be used if the
|
||||
// theme defined a value for this attribute for state CPThemeStateTableDataView
|
||||
|
||||
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
|
||||
{
|
||||
[super setValue:aValue forThemeAttribute:aName];
|
||||
[super setValue:aValue forThemeAttribute:aName inState:CPThemeStateTableDataView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CPTextField (Deprecated)
|
||||
|
||||
- (void)setTextFieldBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
CPLog.error("[CPTextField setTextFieldBackgroundColor:] is deprecated, use [CPTextField setBackgroundColor:] instead.");
|
||||
|
||||
[self setBackgroundColor:aColor];
|
||||
}
|
||||
|
||||
- (CPColor)textFieldBackgroundColor
|
||||
{
|
||||
CPLog.info("[CPTextField textFieldBackgroundColor] is deprecated, use [CPTextField backgroundColor] instead.");
|
||||
|
||||
return [self backgroundColor];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* CPFontDescriptor.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Emmanuel Maillard on 07/03/10.
|
||||
* Copyright Emmanuel Maillard 2010.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
/*
|
||||
Font descriptor dictionary keys
|
||||
*/
|
||||
|
||||
/*
|
||||
CPFontNameAttribute contains a CPString that specified the font name
|
||||
(may be an name list like: 'Marker Felt, Lucida Grande, Helvetica')
|
||||
*/
|
||||
CPFontNameAttribute = @"CPFontNameAttribute";
|
||||
/*
|
||||
CPFontSizeAttribute contains a CPString that specified the font size
|
||||
(as a float value)
|
||||
*/
|
||||
CPFontSizeAttribute = @"CPFontSizeAttribute";
|
||||
/*
|
||||
CPFontTraitsAttribute a CPDictionary that contains font traits keys
|
||||
(CPFontSymbolicTrait or CPFontWeightTrait)
|
||||
*/
|
||||
CPFontTraitsAttribute = @"CPFontTraitsAttribute";
|
||||
|
||||
// Font traits dictionary keys
|
||||
/*
|
||||
CPFontSymbolicTrait a CPNumber that contains CPFontFamilyClass and
|
||||
typeface information flags.
|
||||
*/
|
||||
CPFontSymbolicTrait = @"CPFontSymbolicTrait";
|
||||
|
||||
/*
|
||||
CPFontWeightTrait
|
||||
We use CPString with CSS string values for font weight
|
||||
(normal | bold | bolder | lighter | 100 | 200 | 300 | 400
|
||||
| 500 | 600 | 700 | 800 | 900)
|
||||
NOTE: Cocoa compatibility issue: NSFontWeightTrait are NSNumber for
|
||||
font weight (from -1.0 to 1.0, 0.0 for normal weight).
|
||||
*/
|
||||
CPFontWeightTrait = @"CPFontWeightTrait";
|
||||
|
||||
/*
|
||||
CPFontFamilyClass
|
||||
*/
|
||||
CPFontUnknownClass = 0 << 28;
|
||||
CPFontOldStyleSerifsClass = 1 << 28;
|
||||
CPFontTransitionalSerifsClass = 2 << 28;
|
||||
CPFontModernSerifsClass = 3 << 28;
|
||||
CPFontClarendonSerifsClass = 4 << 28;
|
||||
CPFontSlabSerifsClass = 5 << 28;
|
||||
CPFontFreeformSerifsClass = 7 << 28;
|
||||
CPFontSansSerifClass = 8 << 28;
|
||||
|
||||
CPFontSerifClass = (CPFontOldStyleSerifsClass | CPFontTransitionalSerifsClass |
|
||||
CPFontModernSerifsClass | CPFontClarendonSerifsClass |
|
||||
CPFontSlabSerifsClass | CPFontFreeformSerifsClass);
|
||||
|
||||
CPFontFamilyClassMask = 0xF0000000;
|
||||
|
||||
/*
|
||||
Typeface information
|
||||
*/
|
||||
CPFontItalicTrait = 1 << 0;
|
||||
CPFontBoldTrait = 1 << 1;
|
||||
CPFontExpandedTrait = 1 << 5; /* TODO: CCS 3 font-stretch */
|
||||
CPFontCondensedTrait = 1 << 6;
|
||||
CPFontSmallCapsTrait = 1 << 7;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPFontDescriptor
|
||||
*/
|
||||
@implementation CPFontDescriptor : CPObject
|
||||
{
|
||||
CPDictionary _attributes;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a font descriptor with the specified attributes.
|
||||
|
||||
@param attributes a dictionary that describe the desired font descriptor
|
||||
@return the requested font descriptor
|
||||
*/
|
||||
+ (CPFontDescriptor)fontDescriptorWithFontAttributes:(CPDictionary)attributes
|
||||
{
|
||||
return [[CPFontDescriptor alloc] initWithFontAttributes:attributes];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a font descriptor with the specified name and size.
|
||||
|
||||
@param fontName the name of the font
|
||||
@param aSize the size of the font (in points)
|
||||
@return the requested font descriptor
|
||||
*/
|
||||
+ (CPFontDescriptor)fontDescriptorWithName:(CPString)fontName size:(float)size
|
||||
{
|
||||
return [[CPFontDescriptor alloc] initWithFontAttributes:[CPDictionary dictionaryWithObjects:[fontName, [CPString stringWithString:size + '']] forKeys:[CPFontNameAttribute,CPFontSizeAttribute]]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initialize a font descriptor with the specified attributes.
|
||||
|
||||
@param attributes a dictionary that describe the desired font descriptor
|
||||
@return the requested font descriptor
|
||||
*/
|
||||
- (id)initWithFontAttributes:(CPDictionary)attributes
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_attributes = [[CPMutableDictionary alloc] init];
|
||||
|
||||
if (attributes)
|
||||
[_attributes addEntriesFromDictionary:attributes];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new font descriptor that is the same as the receiver but with the
|
||||
specified attributes taking precedence over the existing ones.
|
||||
|
||||
@param attributes a dictionary that describe the desired font descriptor
|
||||
@return the new font descriptor
|
||||
*/
|
||||
- (CPFontDescriptor)fontDescriptorByAddingAttributes:(CPDictionary)attributes
|
||||
{
|
||||
var attrib = [_attributes copy];
|
||||
|
||||
[attrib addEntriesFromDictionary:attributes];
|
||||
|
||||
return [[CPFontDescriptor alloc] initWithFontAttributes:attrib];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new font descriptor that is the same as the receiver but with the specified size taking precedence over the existing ones.
|
||||
|
||||
@param aSize the new size
|
||||
@return the new font descriptor
|
||||
*/
|
||||
- (CPFontDescriptor)fontDescriptorWithSize:(float)aSize
|
||||
{
|
||||
var attrib = [_attributes copy];
|
||||
|
||||
[attrib setObject:[CPString stringWithString:aSize + ''] forKey:CPFontSizeAttribute];
|
||||
|
||||
return [[CPFontDescriptor alloc] initWithFontAttributes:attrib];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new font descriptor that is the same as the receiver but with
|
||||
the specified symbolic traits taking precedence over the existing ones.
|
||||
|
||||
@param symbolicTraits the desired new symbolic traits
|
||||
@return the new font descriptor
|
||||
*/
|
||||
- (CPFontDescriptor)fontDescriptorWithSymbolicTraits:(CPFontSymbolicTraits)symbolicTraits
|
||||
{
|
||||
var attrib = [_attributes copy];
|
||||
|
||||
if ([attrib objectForKey:CPFontTraitsAttribute])
|
||||
[[attrib objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTraits]
|
||||
forKey:CPFontSymbolicTrait];
|
||||
else
|
||||
[attrib setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTraits]
|
||||
forKey:CPFontSymbolicTrait] forKey:CPFontTraitsAttribute];
|
||||
|
||||
return [[CPFontDescriptor alloc] initWithFontAttributes:attrib];
|
||||
}
|
||||
|
||||
- (id)objectForKey:(id)aKey
|
||||
{
|
||||
return [_attributes objectForKey:aKey];
|
||||
}
|
||||
|
||||
- (CPDictionary)fontAttributes
|
||||
{
|
||||
return _attributes;
|
||||
}
|
||||
|
||||
- (float)pointSize
|
||||
{
|
||||
var value = [_attributes objectForKey:CPFontSizeAttribute];
|
||||
|
||||
return value ? [value floatValue] : 0.0;
|
||||
}
|
||||
|
||||
- (CPFontSymbolicTraits)symbolicTraits
|
||||
{
|
||||
var traits = [_attributes objectForKey:CPFontTraitsAttribute];
|
||||
|
||||
return (traits && [traits objectForKey:CPFontSymbolicTrait]) ? [[traits objectForKey:CPFontSymbolicTrait] unsignedIntValue] : 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPFontDescriptorAttributesKey = @"CPFontDescriptorAttributesKey";
|
||||
|
||||
@implementation CPFontDescriptor (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the font descriptor from a coder.
|
||||
|
||||
@param aCoder the coder from which to read the font descriptor data
|
||||
@return the initialized font
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
return [self initWithFontAttributes:[aCoder decodeObjectForKey:CPFontDescriptorAttributesKey]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the font descriptor to a coder.
|
||||
|
||||
@param aCoder the coder to which the data will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_attributes forKey:CPFontDescriptorAttributesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _wrapNameRegEx = new RegExp(/(\w+\s+\w+)(,*)/g);
|
||||
|
||||
/*
|
||||
Helper methods to CPFont for generating CSS font style
|
||||
*/
|
||||
@implementation CPFontDescriptor (CPFontCSSHelper)
|
||||
|
||||
- (CPString)fontStyleCSSString
|
||||
{
|
||||
return [self symbolicTraits] & CPFontItalicTrait ? @"italic" : @"normal";
|
||||
}
|
||||
|
||||
- (CPString)fontWeightCSSString
|
||||
{
|
||||
var traitsAttributes = [_attributes objectForKey:CPFontTraitsAttribute];
|
||||
|
||||
if (traitsAttributes)
|
||||
{
|
||||
/* give preference to CPFontWeightTrait */
|
||||
if ([traitsAttributes objectForKey:CPFontWeightTrait])
|
||||
return [traitsAttributes objectForKey:CPFontWeightTrait];
|
||||
/* else fallback to facetype symbolic traits */
|
||||
if ([self symbolicTraits] & CPFontBoldTrait)
|
||||
return @"bold";
|
||||
}
|
||||
|
||||
return @"normal";
|
||||
}
|
||||
|
||||
- (CPString)fontSizeCSSString
|
||||
{
|
||||
return [_attributes objectForKey:CPFontSizeAttribute] ? [[_attributes objectForKey:CPFontSizeAttribute] intValue] + "px" : @"";
|
||||
}
|
||||
|
||||
- (CPString)fontFamilyCSSString
|
||||
{
|
||||
var aName = @"";
|
||||
|
||||
if ([_attributes objectForKey:CPFontNameAttribute])
|
||||
aName += [_attributes objectForKey:CPFontNameAttribute].replace(_wrapNameRegEx, '"$1"$2');
|
||||
|
||||
var symbolicTraits = [self symbolicTraits];
|
||||
|
||||
if (symbolicTraits)
|
||||
{
|
||||
if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSansSerifClass)
|
||||
aName += @", sans-serif";
|
||||
else if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSerifClass)
|
||||
aName += @", serif";
|
||||
}
|
||||
|
||||
return aName;
|
||||
}
|
||||
|
||||
- (CPString)fontVariantCSSString
|
||||
{
|
||||
if ([self symbolicTraits] & CPFontSmallCapsTrait)
|
||||
return @"small-caps";
|
||||
|
||||
return @"normal";
|
||||
}
|
||||
|
||||
- (CPString)cssString
|
||||
{
|
||||
return [CPString stringWithString:[self fontStyleCSSString] + " "
|
||||
+ [self fontVariantCSSString] + " "
|
||||
+ [self fontWeightCSSString] + " "
|
||||
+ [self fontSizeCSSString] + " "
|
||||
+ [self fontFamilyCSSString]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* CPFontPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* TODOs:
|
||||
* 1. make browser-width for size smaller and fix columns
|
||||
* 2. add all the missing features from the MacOS X counterpart (sampleview)
|
||||
*
|
||||
*
|
||||
* Created by Daniel Boehringer on 2/JAN/2014.
|
||||
* All modifications copyright Daniel Boehringer 2013.
|
||||
* Extensive code formatting and review by Andrew Hankinson
|
||||
* Based on original work by
|
||||
* Created by Emmanuel Maillard on 06/03/2010.
|
||||
* Copyright Emmanuel Maillard 2010.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
|
||||
@import "CPPanel.j"
|
||||
@import "CPColorWell.j"
|
||||
@import "CPColorPanel.j"
|
||||
@import "CPBrowser.j"
|
||||
@import "CPText.j"
|
||||
@import "CPFontManager.j"
|
||||
|
||||
|
||||
@class CPTextStorage
|
||||
@class CPLayoutManager
|
||||
@class CPTextContainer
|
||||
@class CPFontManager
|
||||
|
||||
/*
|
||||
Collection indexes
|
||||
*/
|
||||
var kTypefaceIndex_Normal = 0,
|
||||
kTypefaceIndex_Italic = 1,
|
||||
kTypefaceIndex_Bold = 2,
|
||||
kTypefaceIndex_BoldItalic = 3,
|
||||
kToolbarHeight = 32,
|
||||
kBorderSpacing = 6,
|
||||
kInnerSpacing = 2,
|
||||
kNothingChanged = 0,
|
||||
kFontNameChanged = 1,
|
||||
kTypefaceChanged = 2,
|
||||
kSizeChanged = 3,
|
||||
kTextColorChanged = 4,
|
||||
kBackgroundColorChanged = 5,
|
||||
kUnderlineChanged = 6,
|
||||
kWeightChanged = 7,
|
||||
_sharedFontPanel;
|
||||
|
||||
// FIXME<!> Locale support
|
||||
var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
|
||||
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"];
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPFontPanel
|
||||
*/
|
||||
@implementation CPFontPanel : CPPanel
|
||||
{
|
||||
id _fontBrowser;
|
||||
id _traitBrowser;
|
||||
id _sizeBrowser;
|
||||
CPArray _availableFonts;
|
||||
id _textColorWell;
|
||||
CPColor _textColor;
|
||||
int _currentColorButtonTag;
|
||||
BOOL _setupDone;
|
||||
int _fontChanges;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
/*!
|
||||
Check if the shared Font panel exists.
|
||||
*/
|
||||
+ (BOOL)sharedFontPanelExists
|
||||
{
|
||||
return _sharedFontPanel != nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the shared Font panel.
|
||||
*/
|
||||
+ (CPFontPanel)sharedFontPanel
|
||||
{
|
||||
if (!_sharedFontPanel)
|
||||
_sharedFontPanel = [[CPFontPanel alloc] init];
|
||||
|
||||
return _sharedFontPanel;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Init methods
|
||||
|
||||
/*! @ignore */
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )])
|
||||
{
|
||||
[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
|
||||
[self setTitle:@"Font Panel"];
|
||||
[self setLevel:CPFloatingWindowLevel];
|
||||
[self setFloatingPanel:YES];
|
||||
[self setBecomesKeyOnlyIfNeeded:YES];
|
||||
[self setMinSize:CGSizeMake(378, 394)];
|
||||
|
||||
_availableFonts = [[CPFontManager sharedFontManager] availableFonts];
|
||||
_textColor = [CPColor blackColor];
|
||||
_setupDone = NO;
|
||||
_fontChanges = kNothingChanged;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_setupToolbarView
|
||||
{
|
||||
_toolbarView = [[CPView alloc] initWithFrame:CGRectMake(0, kBorderSpacing, CGRectGetWidth([self frame]), kToolbarHeight)];
|
||||
[_toolbarView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// Text color
|
||||
_textColorWell = [[CPColorWell alloc] initWithFrame:CGRectMake(10, 0, 25, 25)];
|
||||
[_textColorWell setColor:_textColor];
|
||||
[_toolbarView addSubview:_textColorWell];
|
||||
}
|
||||
|
||||
- (void)_setupBrowser:(CPBrowser)aBrowser
|
||||
{
|
||||
[aBrowser setTarget:self];
|
||||
[aBrowser setAction:@selector(browserClicked:)];
|
||||
[aBrowser setDoubleAction:@selector(dblClicked:)];
|
||||
[aBrowser setAllowsEmptySelection:NO];
|
||||
[aBrowser setAllowsMultipleSelection:NO];
|
||||
[aBrowser setDelegate:self];
|
||||
[[self contentView] addSubview:aBrowser];
|
||||
}
|
||||
|
||||
- (void)_setupContents
|
||||
{
|
||||
if (_setupDone)
|
||||
return;
|
||||
|
||||
_setupDone = YES;
|
||||
|
||||
[self _setupToolbarView];
|
||||
|
||||
var contentView = [self contentView],
|
||||
label = [CPTextField labelWithTitle:@"Font name"],
|
||||
contentBounds = [contentView bounds],
|
||||
upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))];
|
||||
|
||||
[contentView addSubview:_toolbarView];
|
||||
|
||||
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)];
|
||||
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)];
|
||||
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)];
|
||||
|
||||
[self _setupBrowser:_fontBrowser];
|
||||
[self _setupBrowser:_traitBrowser];
|
||||
[self _setupBrowser:_sizeBrowser];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(textViewDidChangeSelection:)
|
||||
name:CPTextViewDidChangeSelectionNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(CPNotification)notification
|
||||
{
|
||||
[self _refreshWithTextView:[notification object]];
|
||||
|
||||
}
|
||||
|
||||
- (void)_refreshWithTextView:(CPTextView)textView
|
||||
{
|
||||
if (![self isVisible])
|
||||
return;
|
||||
|
||||
var attribs = [textView _attributesForFontPanel],
|
||||
font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0],
|
||||
color = [attribs objectForKey:CPForegroundColorAttributeName];
|
||||
|
||||
if (!font)
|
||||
return;
|
||||
|
||||
var trait = kTypefaceIndex_Normal;
|
||||
|
||||
if ([font isItalic] && [font isBold])
|
||||
trait = kTypefaceIndex_BoldItalic;
|
||||
else if ([font isItalic])
|
||||
trait = kTypefaceIndex_Italic;
|
||||
else if ([font isBold])
|
||||
trait = kTypefaceIndex_Bold;
|
||||
|
||||
[self setCurrentFont:font];
|
||||
[self setCurrentTrait:trait];
|
||||
[self setCurrentSize:[font size] + ""]; //cast to string
|
||||
|
||||
if (!color)
|
||||
return;
|
||||
|
||||
[_textColorWell setColor:color];
|
||||
}
|
||||
|
||||
- (void)orderFront:(id)sender
|
||||
{
|
||||
[self _setupContents];
|
||||
[super orderFront:sender];
|
||||
[self _refreshWithTextView:[[CPApp keyWindow] firstResponder]];
|
||||
}
|
||||
|
||||
- (void)reloadDefaultFontFamilies
|
||||
{
|
||||
_availableFonts = [[CPFontManager sharedFontManager] availableFonts];
|
||||
}
|
||||
|
||||
- (BOOL)worksWhenModal
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
@param aFont the font to convert.
|
||||
@return The converted font or \c aFont if failed to convert.
|
||||
*/
|
||||
- (CPFont)panelConvertFont:(CPFont)aFont
|
||||
{
|
||||
var newFont = aFont,
|
||||
index = 0;
|
||||
|
||||
switch (_fontChanges)
|
||||
{
|
||||
case kFontNameChanged:
|
||||
newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes:
|
||||
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
|
||||
break;
|
||||
|
||||
case kTypefaceChanged:
|
||||
index = [self currentTrait];
|
||||
if (index == kTypefaceIndex_BoldItalic)
|
||||
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask | CPItalicFontMask];
|
||||
else if (index == kTypefaceIndex_Bold)
|
||||
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask];
|
||||
else if (index == kTypefaceIndex_Italic)
|
||||
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPItalicFontMask];
|
||||
else
|
||||
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toNotHaveTrait:CPBoldFontMask | CPItalicFontMask];
|
||||
break;
|
||||
|
||||
case kSizeChanged:
|
||||
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]];
|
||||
break;
|
||||
|
||||
case kNothingChanged:
|
||||
break;
|
||||
|
||||
default:
|
||||
CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges);
|
||||
break;
|
||||
}
|
||||
|
||||
return newFont;
|
||||
}
|
||||
|
||||
- (void)setCurrentSize:(CGSize)aSize
|
||||
{
|
||||
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
|
||||
}
|
||||
|
||||
- (CPString)currentSize
|
||||
{
|
||||
return [_sizeBrowser selectedItem];
|
||||
}
|
||||
|
||||
- (void)setCurrentFont:(CPFont)aFont
|
||||
{
|
||||
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
|
||||
}
|
||||
|
||||
- (CPString)currentFont
|
||||
{
|
||||
return [_fontBrowser selectedItem];
|
||||
}
|
||||
|
||||
- (void)setCurrentTrait:(unsigned)aTrait
|
||||
{
|
||||
var row = 0;
|
||||
|
||||
switch (aTrait)
|
||||
{
|
||||
case kTypefaceIndex_Italic:
|
||||
row = 1;
|
||||
break;
|
||||
|
||||
case kTypefaceIndex_Bold:
|
||||
row = 2;
|
||||
break;
|
||||
|
||||
case kTypefaceIndex_BoldItalic:
|
||||
row = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
[_traitBrowser selectRow:row inColumn:0];
|
||||
}
|
||||
|
||||
// FIXME<!> Locale support
|
||||
- (unsigned)currentTrait
|
||||
{
|
||||
var sel = [_traitBrowser selectedItem];
|
||||
|
||||
if (sel === "Italic")
|
||||
return kTypefaceIndex_Italic;
|
||||
|
||||
if (sel === "Bold")
|
||||
return kTypefaceIndex_Bold;
|
||||
|
||||
if (sel === "Bold Italic")
|
||||
return kTypefaceIndex_BoldItalic;
|
||||
|
||||
return kTypefaceIndex_Normal;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the selected font in Font panel.
|
||||
@param font the selected font
|
||||
@param flag if \c the current selection have multiple fonts.
|
||||
*/
|
||||
- (void)setPanelFont:(CPFont)font isMultiple:(BOOL)flag
|
||||
{
|
||||
[self _setupContents];
|
||||
|
||||
if ([self currentFont] !== [font familyName])
|
||||
[self setCurrentFont:[font familyName]];
|
||||
|
||||
if ([self currentSize] != [font size])
|
||||
[self setCurrentSize:[font size]];
|
||||
|
||||
var typefaceIndex = kTypefaceIndex_Normal,
|
||||
symbolicTraits = [[font fontDescriptor] symbolicTraits];
|
||||
|
||||
if ((symbolicTraits & CPFontItalicTrait) && (symbolicTraits & CPFontBoldTrait))
|
||||
typefaceIndex = kTypefaceIndex_BoldItalic;
|
||||
else if (symbolicTraits & CPFontItalicTrait)
|
||||
typefaceIndex = kTypefaceIndex_Italic;
|
||||
else if (symbolicTraits & CPFontBoldTrait)
|
||||
typefaceIndex = kTypefaceIndex_Bold;
|
||||
|
||||
if ([self currentTrait] != typefaceIndex)
|
||||
[self setCurrentTrait:typefaceIndex ];
|
||||
|
||||
_fontChanges = kNothingChanged;
|
||||
}
|
||||
|
||||
- (void)changeColor:(id)sender
|
||||
{
|
||||
_textColor = [sender color];
|
||||
_fontChanges = kTextColorChanged;
|
||||
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// TODO: ask CPFontManager for traits //
|
||||
- (void)browserClicked:(id)aBrowser
|
||||
{
|
||||
if (aBrowser === _fontBrowser)
|
||||
{
|
||||
_fontChanges = kFontNameChanged;
|
||||
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
|
||||
}
|
||||
else if (aBrowser === _traitBrowser)
|
||||
{
|
||||
_fontChanges = kTypefaceChanged;
|
||||
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
|
||||
}
|
||||
else if (aBrowser === _sizeBrowser)
|
||||
{
|
||||
_fontChanges = kSizeChanged;
|
||||
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dblClicked:(id)sender
|
||||
{
|
||||
// alert("DOUBLE");
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser numberOfChildrenOfItem:(id)anItem
|
||||
{
|
||||
if (aBrowser === _fontBrowser)
|
||||
return [_availableFonts count];
|
||||
|
||||
if (aBrowser === _traitBrowser)
|
||||
return [_availableTraits count];
|
||||
|
||||
return [_availableSizes count]
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem
|
||||
{
|
||||
if (aBrowser === _fontBrowser)
|
||||
return [_availableFonts objectAtIndex:index];
|
||||
|
||||
if (aBrowser === _traitBrowser)
|
||||
return [_availableTraits objectAtIndex:index];
|
||||
|
||||
return [_availableSizes objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser objectValueForItem:(id)anItem
|
||||
{
|
||||
return anItem;
|
||||
}
|
||||
|
||||
- (BOOL)browser:(id)aBrowser isLeafItem:(id)anItem
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
[CPFontManager setFontPanelFactory:[CPFontPanel class]];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* CPParagraphStyle.j
|
||||
* AppKit
|
||||
*
|
||||
* FIXME
|
||||
* This is basically a stub.
|
||||
* We need to store all the spacing informations as well as writing direction (among others)
|
||||
*
|
||||
* Created by Daniel Boehringer on 11/01/2014
|
||||
* Copyright Daniel Boehringer 2014.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPText.j"
|
||||
|
||||
CPLeftTabStopType = 0;
|
||||
|
||||
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
|
||||
|
||||
var _sharedDefaultParagraphStyle,
|
||||
_defaultTabStopArray;
|
||||
|
||||
@implementation CPParagraphStyle : CPObject
|
||||
{
|
||||
CPArray _tabStops @accessors(property=tabStops);
|
||||
CPTextAlignment _alignment @accessors(property=alignment);
|
||||
unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent);
|
||||
unsigned _headIndent @accessors(property=headIndent);
|
||||
unsigned _tailIndent @accessors(property=tailIndent);
|
||||
unsigned _paragraphSpacing @accessors(property=paragraphSpacing);
|
||||
unsigned _minimumLineHeight @accessors(property=minimumLineHeight);
|
||||
unsigned _maximumLineHeight @accessors(property=maximumLineHeight);
|
||||
unsigned _lineSpacing @accessors(property=lineSpacing);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (CPParagraphStyle)defaultParagraphStyle
|
||||
{
|
||||
if (!_sharedDefaultParagraphStyle)
|
||||
_sharedDefaultParagraphStyle = [self new];
|
||||
|
||||
return _sharedDefaultParagraphStyle;
|
||||
}
|
||||
|
||||
+ (CPArray)_defaultTabStops
|
||||
{
|
||||
if (!_defaultTabStopArray)
|
||||
{
|
||||
var i;
|
||||
_defaultTabStopArray = [];
|
||||
|
||||
// <!> FIXME: Define constants for these magic numbers: 13, 28
|
||||
for (i = 1; i < 16 ; i++)
|
||||
{
|
||||
_defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]);
|
||||
}
|
||||
}
|
||||
|
||||
return _defaultTabStopArray;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Init methods
|
||||
|
||||
- (id)init
|
||||
{
|
||||
[self _initWithDefaults];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_tabStops = [other._tabStops copy];
|
||||
_alignment = other._alignment;
|
||||
_firstLineHeadIndent = other._firstLineHeadIndent;
|
||||
_headIndent = other._headIndent;
|
||||
_tailIndent = other._tailIndent;
|
||||
_paragraphSpacing = other._paragraphSpacing;
|
||||
_minimumLineHeight = other._minimumLineHeight;
|
||||
_maximumLineHeight = other._maximumLineHeight;
|
||||
_lineSpacing = other._lineSpacing;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initWithDefaults
|
||||
{
|
||||
_alignment = CPLeftTextAlignment;
|
||||
_tabStops = [[[self class] _defaultTabStops] copy];
|
||||
}
|
||||
|
||||
- (void)addTabStop:(CPTextTab)aStop
|
||||
{
|
||||
_tabStops.push(aStop);
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var other = [[self class] alloc];
|
||||
|
||||
return [other initWithParagraphStyle:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey",
|
||||
CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey",
|
||||
CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey",
|
||||
CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey",
|
||||
CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey",
|
||||
CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey",
|
||||
CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey",
|
||||
CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey",
|
||||
CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey";
|
||||
|
||||
@implementation CPParagraphStyle (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(id)aCoder
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"];
|
||||
_alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"];
|
||||
_firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"];
|
||||
_headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"];
|
||||
_tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"];
|
||||
_paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"];
|
||||
_minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"];
|
||||
_maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"];
|
||||
_lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(id)aCoder
|
||||
{
|
||||
[aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"];
|
||||
[aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"];
|
||||
[aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"];
|
||||
[aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"];
|
||||
[aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"];
|
||||
[aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"];
|
||||
[aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"];
|
||||
[aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"];
|
||||
[aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPTextTab : CPObject
|
||||
{
|
||||
int _type @accessors(property = tabStopType);
|
||||
double _location @accessors(property = location);
|
||||
}
|
||||
|
||||
- (id)initWithType:(CPTabStopType) aType location:(double) aLocation
|
||||
{
|
||||
if ([self = [super init]])
|
||||
{
|
||||
_type = aType;
|
||||
_location = aLocation;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPTextTabTypeKey = @"CPTextTabTypeKey",
|
||||
CPTextTabLocationKey = @"CPTextTabLocationKey";
|
||||
|
||||
@implementation CPTextTab (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(id)aCoder
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = [aCoder decodeIntForKey:"CPTextTabTypeKey"];
|
||||
_location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(id)aCoder
|
||||
{
|
||||
[aCoder encodeInt:_type forKey:"CPTextTabTypeKey"];
|
||||
[aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* CPTextContainer.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Emmanuel Maillard on 27/02/2010.
|
||||
* Copyright Emmanuel Maillard 2010.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPGeometry.j>
|
||||
@import "CPLayoutManager.j"
|
||||
|
||||
@class CPTextView
|
||||
@class CPLayoutManager
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPLineSweepDirection
|
||||
*/
|
||||
CPLineSweepLeft = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPLineSweepDirection
|
||||
*/
|
||||
CPLineSweepRight = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPLineSweepDirection
|
||||
*/
|
||||
CPLineSweepDown = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPLineSweepDirection
|
||||
*/
|
||||
CPLineSweepUp = 3;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPLineMovementDirection
|
||||
*/
|
||||
CPLineDoesntMoves = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPLineMovementDirection
|
||||
*/
|
||||
CPLineMovesLeft = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPLineMovementDirection
|
||||
*/
|
||||
CPLineMovesRight = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPLineMovementDirection
|
||||
*/
|
||||
CPLineMovesDown = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPLineMovementDirection
|
||||
*/
|
||||
CPLineMovesUp = 4;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPTextContainer
|
||||
*/
|
||||
@implementation CPTextContainer : CPObject
|
||||
{
|
||||
float _lineFragmentPadding @accessors(property=lineFragmentPadding);
|
||||
CGSize _size @accessors(property=containerSize);
|
||||
CPLayoutManager _layoutManager @accessors(property=layoutManager);
|
||||
CPTextView _textView @accessors(property=textView);
|
||||
BOOL _inResizing;
|
||||
BOOL _widthTracksTextView;
|
||||
BOOL _heightTracksTextView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Init methods
|
||||
|
||||
- (id)initWithContainerSize:(CGSize)aSize
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = aSize;
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithContainerSize:CPMakeSize(1e7, 1e7)];
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_lineFragmentPadding = 0.0;
|
||||
|
||||
_layoutManager = [[CPLayoutManager alloc] init];
|
||||
[_layoutManager addTextContainer:self];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Setter methods
|
||||
|
||||
- (void)setContainerSize:(CGSize)someSize
|
||||
{
|
||||
var oldSize = _size;
|
||||
|
||||
_size = CGSizeMakeCopy(someSize);
|
||||
|
||||
if (oldSize.width != _size.width)
|
||||
{
|
||||
_inResizing = YES;
|
||||
[_layoutManager invalidateLayoutForCharacterRange:CPMakeRange(0, [[_layoutManager textStorage] length])
|
||||
isSoft:NO
|
||||
actualCharacterRange:NULL];
|
||||
|
||||
[_layoutManager _validateLayoutAndGlyphs];
|
||||
[_textView sizeToFit]; // this is necessary to adopt the height of CPTextView in case of rewrapping
|
||||
_inResizing = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized.
|
||||
- (BOOL)widthTracksTextView
|
||||
{
|
||||
return _widthTracksTextView;
|
||||
}
|
||||
|
||||
- (void)setWidthTracksTextView:(BOOL)flag
|
||||
{
|
||||
_widthTracksTextView = flag;
|
||||
[_textView setPostsFrameChangedNotifications:flag];
|
||||
|
||||
if (flag && _textView)
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(textViewFrameChanged:)
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_textView];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_textView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textViewFrameChanged:(CPNotification)aNotification
|
||||
{
|
||||
var newSize = CGSizeMake([_textView frame].size.width, _size.height);
|
||||
|
||||
[self setContainerSize:newSize];
|
||||
}
|
||||
|
||||
- (void)setTextView:(CPTextView)aTextView
|
||||
{
|
||||
if (_textView)
|
||||
{
|
||||
[self setWidthTracksTextView:NO]; // We only support width
|
||||
[_textView setTextContainer:nil];
|
||||
}
|
||||
|
||||
_textView = aTextView;
|
||||
|
||||
if (_textView)
|
||||
{
|
||||
[self setWidthTracksTextView:_widthTracksTextView]; // We only support width
|
||||
[_textView setTextContainer:self];
|
||||
}
|
||||
|
||||
[_layoutManager textContainerChangedTextView:self];
|
||||
}
|
||||
|
||||
- (BOOL)containsPoint:(CGPoint)aPoint
|
||||
{
|
||||
return CGRectContainsPoint(CGRectMake(0, 0, _size.width, _size.height), aPoint);
|
||||
}
|
||||
|
||||
- (BOOL)isSimpleRectangularTextContainer
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect
|
||||
sweepDirection:(CPLineSweepDirection)sweep
|
||||
movementDirection:(CPLineMovementDirection)movement
|
||||
remainingRect:(CGRectPointer)remainingRect
|
||||
{
|
||||
var resultRect = CGRectCreateCopy(proposedRect);
|
||||
|
||||
if (sweep != CPLineSweepRight || movement != CPLineMovesDown)
|
||||
{
|
||||
CPLog.trace(@"FIXME: unsupported sweep (" + sweep + ") or movement (" + movement + ")");
|
||||
return CGRectMakeZero();
|
||||
}
|
||||
|
||||
if (resultRect.origin.x + resultRect.size.width > _size.width)
|
||||
resultRect.size.width = _size.width - resultRect.origin.x;
|
||||
|
||||
if (resultRect.size.width < 0)
|
||||
resultRect = CGRectMakeZero();
|
||||
|
||||
if (remainingRect)
|
||||
{
|
||||
remainingRect.origin.x = resultRect.origin.x + resultRect.size.width;
|
||||
remainingRect.origin.y = resultRect.origin.y;
|
||||
remainingRect.size.height = resultRect.size.height;
|
||||
remainingRect.size.width = _size.width - (resultRect.origin.x + resultRect.size.width);
|
||||
}
|
||||
|
||||
return resultRect;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
|
||||
CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey",
|
||||
CPTextContainerWidthTracksTextViewKey = @"CPTextContainerWidthTracksTextViewKey",
|
||||
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey";
|
||||
|
||||
@implementation CPTextContainer (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self _init];
|
||||
|
||||
_size = [aCoder decodeSizeForKey:CPTextContainerSizeKey];
|
||||
|
||||
_layoutManager = [aCoder decodeObjectForKey:CPTextContainerLayoutManagerKey];
|
||||
[_layoutManager addTextContainer:self];
|
||||
|
||||
_widthTracksTextView = [aCoder decodeBoolForKey:CPTextContainerWidthTracksTextViewKey];
|
||||
_heightTracksTextView = [aCoder decodeBoolForKey:CPTextContainerHeightTracksTextViewKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeSize:_size forKey:CPTextContainerSizeKey];
|
||||
[aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey];
|
||||
[aCoder encodeBool:_widthTracksTextView forKey:CPTextContainerWidthTracksTextViewKey];
|
||||
[aCoder encodeBool:_heightTracksTextView forKey:CPTextContainerHeightTracksTextViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* CPTextStorage.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Emmanuel Maillard on 27/02/2010.
|
||||
* Copyright Emmanuel Maillard 2010.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPAttributedString.j>
|
||||
|
||||
@import "CPText.j"
|
||||
@import "CPFont.j"
|
||||
|
||||
@class CPLayoutManager;
|
||||
|
||||
CPTextStorageEditedAttributes = 1;
|
||||
CPTextStorageEditedCharacters = 2;
|
||||
|
||||
CPTextStorageWillProcessEditingNotification = @"CPTextStorageWillProcessEditingNotification";
|
||||
CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNotification";
|
||||
|
||||
@protocol CPTextStorageDelegate <CPObject>
|
||||
|
||||
- (void)textStorageWillProcessEditing:(CPNotification)aNotification;
|
||||
- (void)textStorageDidProcessEditing:(CPNotification)aNotification;
|
||||
|
||||
@end
|
||||
|
||||
CPAttachmentCharacter = 65532; // "\ufffc";
|
||||
_CPAttachmentCharacterAsString = String.fromCharCode(CPAttachmentCharacter);
|
||||
|
||||
_CPAttachmentView = "_CPAttachmentView";
|
||||
_CPAttachmentInvisible = "_CPAttachmentInvisible";
|
||||
|
||||
var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
|
||||
CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPTextStorage
|
||||
*/
|
||||
@implementation CPTextStorage : CPMutableAttributedString
|
||||
{
|
||||
CPColor _foregroundColor @accessors(property=foregroundColor);
|
||||
CPFont _font @accessors(property=font);
|
||||
CPMutableArray _layoutManagers @accessors(getter=layoutManagers);
|
||||
CPRange _editedRange @accessors(getter=editedRange);
|
||||
id <CPTextStorageDelegate> _delegate @accessors(property=delegate);
|
||||
int _changeInLength @accessors(property=changeInLength);
|
||||
unsigned _editedMask @accessors(property=editedMask);
|
||||
|
||||
int _editCount; // {begin,end}Editing counter
|
||||
unsigned _implementedDelegateMethods;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Init methods
|
||||
|
||||
- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
|
||||
{
|
||||
self = [super initWithString:aString attributes:attributes];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_layoutManagers = [[CPMutableArray alloc] init];
|
||||
_editedRange = CPMakeRange(CPNotFound, 0);
|
||||
_changeInLength = 0;
|
||||
_editedMask = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithString:(CPString)aString
|
||||
{
|
||||
return [self initWithString:aString attributes:nil];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithString:@"" attributes:nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Delegate methods
|
||||
|
||||
- (void)setDelegate:(id <CPTextStorageDelegate>)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_implementedDelegateMethods = 0;
|
||||
_delegate = aDelegate;
|
||||
|
||||
if (_delegate)
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(textStorageWillProcessEditing:)])
|
||||
_implementedDelegateMethods |= CPTextStorageDelegate_textStorageWillProcessEditing_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(textStorageDidProcessEditing:)])
|
||||
_implementedDelegateMethods |= CPTextStorageDelegate_textStorageDidProcessEditing_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Layout manager methods
|
||||
|
||||
- (void)addLayoutManager:(CPLayoutManager)aManager
|
||||
{
|
||||
if ([_layoutManagers containsObject:aManager])
|
||||
return;
|
||||
|
||||
[aManager setTextStorage:self];
|
||||
[_layoutManagers addObject:aManager];
|
||||
}
|
||||
|
||||
- (void)removeLayoutManager:(CPLayoutManager)aManager
|
||||
{
|
||||
if (![_layoutManagers containsObject:aManager])
|
||||
return;
|
||||
|
||||
[aManager setTextStorage:nil];
|
||||
[_layoutManagers removeObject:aManager];
|
||||
}
|
||||
|
||||
- (void)invalidateAttributesInRange:(CPRange)aRange
|
||||
{
|
||||
/* FIXME: stub */
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Editing methods
|
||||
|
||||
- (void)processEditing
|
||||
{
|
||||
[self _sendDelegateWillProcessEditingNotification];
|
||||
[self invalidateAttributesInRange:[self editedRange]];
|
||||
[self _sendDelegateDidProcessEditingNotification];
|
||||
|
||||
var c = [_layoutManagers count];
|
||||
|
||||
for (var i = 0; i < c; i++)
|
||||
{
|
||||
[[_layoutManagers objectAtIndex:i] textStorage:self
|
||||
edited:_editedMask
|
||||
range:_editedRange
|
||||
changeInLength:_changeInLength
|
||||
invalidatedRange:_editedRange];
|
||||
}
|
||||
|
||||
_editedRange.location = CPNotFound;
|
||||
_editedMask = 0;
|
||||
_changeInLength = 0;
|
||||
}
|
||||
|
||||
- (void)beginEditing
|
||||
{
|
||||
if (_editCount == 0)
|
||||
_editedRange = CPMakeRange(CPNotFound, 0);
|
||||
|
||||
_editCount++;
|
||||
}
|
||||
|
||||
- (void)endEditing
|
||||
{
|
||||
_editCount--;
|
||||
|
||||
if (_editCount == 0)
|
||||
[self processEditing];
|
||||
}
|
||||
|
||||
- (void)edited:(unsigned)editedMask range:(CPRange)aRange changeInLength:(int)lengthChange
|
||||
{
|
||||
var copyRange = CPMakeRangeCopy(aRange);
|
||||
|
||||
if (_editCount == 0) // used outside a beginEditing/endEditing
|
||||
{
|
||||
_editedMask = editedMask;
|
||||
_changeInLength = lengthChange;
|
||||
copyRange.length += lengthChange;
|
||||
_editedRange = copyRange;
|
||||
[self processEditing];
|
||||
}
|
||||
else
|
||||
{
|
||||
_editedMask |= editedMask;
|
||||
_changeInLength += lengthChange;
|
||||
copyRange.length += lengthChange;
|
||||
|
||||
if (_editedRange.location == CPNotFound)
|
||||
_editedRange = copyRange;
|
||||
else
|
||||
_editedRange = CPUnionRange(_editedRange,copyRange);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
|
||||
{
|
||||
[self beginEditing];
|
||||
[super removeAttribute:anAttribute range:aRange];
|
||||
[self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0];
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
|
||||
{
|
||||
[self beginEditing];
|
||||
[super addAttributes:aDictionary range:aRange];
|
||||
[self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0];
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)deleteCharactersInRange:(CPRange)aRange
|
||||
{
|
||||
[self beginEditing];
|
||||
[super deleteCharactersInRange:aRange];
|
||||
[self edited:CPTextStorageEditedCharacters range:aRange changeInLength:-aRange.length];
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
|
||||
{
|
||||
[self beginEditing];
|
||||
[super replaceCharactersInRange:aRange withString:aString];
|
||||
[self edited:CPTextStorageEditedCharacters range:aRange changeInLength:([aString length] - aRange.length)];
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
|
||||
{
|
||||
[self beginEditing];
|
||||
[super replaceCharactersInRange:aRange withAttributedString:aString];
|
||||
[self edited:(CPTextStorageEditedAttributes | CPTextStorageEditedCharacters) range:aRange changeInLength:([aString length] - aRange.length)];
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
|
||||
{
|
||||
if (!aRange.length)
|
||||
return [CPAttributedString new];
|
||||
|
||||
return [super attributedSubstringFromRange:aRange];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an instance of CPTextStorage that contains the provided instance of CPView.
|
||||
This can be used to insert arbitrary views into the text. These views are treated as individual characters during editing.
|
||||
This works only with views that conform to the CPCoding protocol
|
||||
*/
|
||||
|
||||
+ (id)attributedStringWithAttachment:(CPView)someView
|
||||
{
|
||||
var result = [[self alloc] initWithString:_CPAttachmentCharacterAsString];
|
||||
|
||||
[result setAttributes:@{_CPAttachmentView:someView} range:CPMakeRange(0, 1)];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPTextStorage (CPTextStorageDelegate)
|
||||
|
||||
- (void)_sendDelegateWillProcessEditingNotification
|
||||
{
|
||||
if (_implementedDelegateMethods & CPTextStorageDelegate_textStorageWillProcessEditing_)
|
||||
[_delegate textStorageWillProcessEditing:[[CPNotification alloc] initWithName:CPTextStorageWillProcessEditingNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageWillProcessEditingNotification object:self];
|
||||
}
|
||||
|
||||
- (void)_sendDelegateDidProcessEditingNotification
|
||||
{
|
||||
if (_implementedDelegateMethods & CPTextStorageDelegate_textStorageDidProcessEditing_)
|
||||
[_delegate textStorageWillProcessEditing:[[CPNotification alloc] initWithName:CPTextStorageDidProcessEditingNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageDidProcessEditingNotification object:self];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* CPTypesetter.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Daniel Boehringer on 27/12/2013.
|
||||
* All modifications copyright Daniel Boehringer 2013.
|
||||
* Extensive code formatting and review by Andrew Hankinson
|
||||
* Based on original work by
|
||||
* Emmanuel Maillard on 27/02/2010.
|
||||
* Copyright Emmanuel Maillard 2010.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CPParagraphStyle.j"
|
||||
@import "CPTextStorage.j"
|
||||
@import "CPFont.j"
|
||||
|
||||
// forward declare these classes for type matching
|
||||
@class CPLayoutManager
|
||||
@class CPTextContainer
|
||||
@class CPTextView
|
||||
|
||||
/*
|
||||
CPTypesetterControlCharacterAction
|
||||
*/
|
||||
CPTypesetterZeroAdvancementAction = 1 << 0;
|
||||
CPTypesetterWhitespaceAction = 1 << 1;
|
||||
CPSTypesetterHorizontalTabAction = 1 << 2;
|
||||
CPTypesetterLineBreakAction = 1 << 3;
|
||||
CPTypesetterParagraphBreakAction = 1 << 4;
|
||||
CPTypesetterContainerBreakAction = 1 << 5;
|
||||
|
||||
var CPSystemTypesetterFactory,
|
||||
_sharedSimpleTypesetter;
|
||||
|
||||
@implementation CPTypesetter : CPObject
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
[CPTypesetter _setSystemTypesetterFactory:[CPSimpleTypesetter class]];
|
||||
}
|
||||
|
||||
+ (id)sharedSystemTypesetter
|
||||
{
|
||||
return [CPSystemTypesetterFactory sharedInstance];
|
||||
}
|
||||
|
||||
+ (void)_setSystemTypesetterFactory:(Class)aClass
|
||||
{
|
||||
CPSystemTypesetterFactory = aClass;
|
||||
}
|
||||
|
||||
- (CPTypesetterControlCharacterAction)actionForControlCharacterAtIndex:(unsigned)charIndex
|
||||
{
|
||||
return CPTypesetterZeroAdvancementAction;
|
||||
}
|
||||
|
||||
- (CPLayoutManager)layoutManager
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CPTextContainer)currentTextContainer
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CPArray)textContainers
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager
|
||||
startingAtGlyphIndex:(unsigned)startGlyphIndex
|
||||
maxNumberOfLineFragments:(unsigned)maxNumLines
|
||||
nextGlyphIndex:(UIntegerReference)nextGlyph
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPSimpleTypesetter : CPTypesetter
|
||||
{
|
||||
CPLayoutManager _layoutManager @accessors(property=layoutManager);
|
||||
CPTextContainer _currentTextContainer @accessors(property=currentTextContainer);
|
||||
CPTextStorage _textStorage;
|
||||
|
||||
CPRange _attributesRange;
|
||||
CPDictionary _currentAttributes;
|
||||
CPParagraphStyle _currentParagraph;
|
||||
|
||||
float _lineHeight;
|
||||
float _lineBase;
|
||||
float _lineWidth;
|
||||
|
||||
unsigned _indexOfCurrentContainer;
|
||||
|
||||
CPArray _lineFragments;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (id)sharedInstance
|
||||
{
|
||||
if (!_sharedSimpleTypesetter)
|
||||
_sharedSimpleTypesetter = [[CPSimpleTypesetter alloc] init];
|
||||
|
||||
return _sharedSimpleTypesetter;
|
||||
}
|
||||
|
||||
- (CPArray)textContainers
|
||||
{
|
||||
return [_layoutManager textContainers];
|
||||
}
|
||||
|
||||
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
|
||||
{
|
||||
var tabStops = [_currentParagraph tabStops];
|
||||
|
||||
if (!tabStops)
|
||||
tabStops = [CPParagraphStyle _defaultTabStops];
|
||||
|
||||
var l = tabStops.length;
|
||||
|
||||
if (aWidth > tabStops[l - 1]._location)
|
||||
return nil;
|
||||
|
||||
for (var i = l - 1; i >= 0; i--)
|
||||
{
|
||||
if (aWidth > tabStops[i]._location)
|
||||
{
|
||||
if (i + 1 < l)
|
||||
return tabStops[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (i === -1)
|
||||
return tabStops[0];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)_flushRange:(CPRange)lineRange
|
||||
lineOrigin:(CGPoint)lineOrigin
|
||||
currentContainer:(CPTextContainer)aContainer
|
||||
advancements:(CPArray)advancements
|
||||
lineCount:(unsigned)lineCount
|
||||
sameLine:(BOOL)sameLine
|
||||
{
|
||||
var myX = 0,
|
||||
rect = CGRectMake(lineOrigin.x, lineOrigin.y, _lineWidth, _lineHeight),
|
||||
containerSize = aContainer._size;
|
||||
|
||||
[_layoutManager _appendNewLineFragmentInTextContainer:_currentTextContainer forGlyphRange:lineRange];
|
||||
|
||||
var fragment = [_layoutManager._lineFragments lastObject];
|
||||
fragment._isLast = !sameLine;
|
||||
_lineFragments.push(fragment);
|
||||
|
||||
[_layoutManager setLineFragmentRect:rect forGlyphRange:lineRange usedRect:rect];
|
||||
|
||||
switch ([_currentParagraph alignment])
|
||||
{
|
||||
case CPLeftTextAlignment:
|
||||
myX = 0;
|
||||
break;
|
||||
|
||||
case CPCenterTextAlignment:
|
||||
myX = (containerSize.width - _lineWidth) / 2;
|
||||
break;
|
||||
|
||||
case CPRightTextAlignment:
|
||||
myX = containerSize.width - _lineWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
[_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange];
|
||||
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
|
||||
|
||||
//fix the _lineFragments when fontsizes differ
|
||||
var l = _lineFragments.length;
|
||||
|
||||
for (var i = 0 ; i < l ; i++)
|
||||
[_lineFragments[i] _adjustForHeight:_lineHeight];
|
||||
|
||||
if (!lineCount) // do not rescue on first line
|
||||
return NO;
|
||||
|
||||
if (aContainer._inResizing)
|
||||
return NO;
|
||||
|
||||
return ([_layoutManager _rescuingInvalidFragmentsWasPossibleForGlyphRange:lineRange]);
|
||||
}
|
||||
|
||||
- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager
|
||||
startingAtGlyphIndex:(unsigned)glyphIndex
|
||||
maxNumberOfLineFragments:(unsigned)maxNumLines
|
||||
nextGlyphIndex:(UIntegerReference)nextGlyph
|
||||
{
|
||||
var textContainers = [layoutManager textContainers],
|
||||
textContainersCount = [textContainers count];
|
||||
|
||||
_layoutManager = layoutManager;
|
||||
_textStorage = [_layoutManager textStorage];
|
||||
_indexOfCurrentContainer = MAX(0, [textContainers
|
||||
indexOfObject:[_layoutManager textContainerForGlyphAtIndex:glyphIndex effectiveRange:nil withoutAdditionalLayout:YES]
|
||||
inRange:CPMakeRange(0, textContainersCount)]);
|
||||
|
||||
_currentTextContainer = textContainers[_indexOfCurrentContainer];
|
||||
|
||||
_attributesRange = CPMakeRange(0, 0);
|
||||
_lineHeight = 0;
|
||||
_lineBase = 0;
|
||||
_lineWidth = 0;
|
||||
|
||||
var containerSize = [_currentTextContainer containerSize],
|
||||
containerSizeWidth = containerSize.width,
|
||||
containerSizeHeight = containerSize.height,
|
||||
lineRange = CPMakeRange(glyphIndex, 0),
|
||||
wrapRange = CPMakeRange(0, 0),
|
||||
wrapWidth = 0,
|
||||
isNewline = NO,
|
||||
isTabStop = NO,
|
||||
isAttachment = NO,
|
||||
isWordWrapped = NO,
|
||||
numberOfGlyphs= [_textStorage length],
|
||||
leading,
|
||||
numLines = 0,
|
||||
theString = [_textStorage string],
|
||||
lineOrigin,
|
||||
ascent,
|
||||
descent,
|
||||
advancements = [],
|
||||
prevRangeWidth = 0,
|
||||
measuringRange = CPMakeRange(glyphIndex, 0),
|
||||
currentAnchor = 0,
|
||||
currentFont,
|
||||
currentFontLineHeight,
|
||||
previousFont,
|
||||
currentParagraphMinimumLineHeight,
|
||||
currentParagraphMaximumLineHeight,
|
||||
currentParagraphLineSpacing;
|
||||
|
||||
if (glyphIndex > 0)
|
||||
lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
|
||||
else if ([_layoutManager extraLineFragmentTextContainer])
|
||||
lineOrigin = CGPointMake(0, [_layoutManager extraLineFragmentUsedRect].origin.y);
|
||||
else
|
||||
lineOrigin = CGPointMake(0, 0);
|
||||
|
||||
[_layoutManager _removeInvalidLineFragments];
|
||||
|
||||
if (![_textStorage length])
|
||||
return;
|
||||
|
||||
_lineFragments = [];
|
||||
|
||||
for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++)
|
||||
{
|
||||
// check whether there any change in the attributes from here on
|
||||
if (!CPLocationInRange(glyphIndex, _attributesRange))
|
||||
{
|
||||
_currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange];
|
||||
currentFont = [_currentAttributes objectForKey:CPFontAttributeName];
|
||||
_currentParagraph = [_currentAttributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle];
|
||||
currentParagraphMinimumLineHeight = [_currentParagraph minimumLineHeight];
|
||||
currentParagraphMaximumLineHeight = [_currentParagraph maximumLineHeight];
|
||||
currentParagraphLineSpacing = [_currentParagraph lineSpacing];
|
||||
|
||||
if (!currentFont)
|
||||
currentFont = [_textStorage font] || [CPFont systemFontOfSize:12.0];
|
||||
|
||||
ascent = [currentFont ascender];
|
||||
descent = [currentFont descender];
|
||||
leading = (ascent - descent) * 0.2; // FAKE leading
|
||||
|
||||
currentFontLineHeight = ascent - descent + leading;
|
||||
|
||||
if (previousFont !== currentFont)
|
||||
{
|
||||
measuringRange = CPMakeRange(glyphIndex, 0);
|
||||
currentAnchor = prevRangeWidth;
|
||||
previousFont = currentFont;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (currentFontLineHeight > _lineHeight)
|
||||
_lineHeight = currentFontLineHeight;
|
||||
|
||||
if (ascent > _lineBase)
|
||||
_lineBase = ascent;
|
||||
|
||||
lineRange.length++;
|
||||
measuringRange.length++;
|
||||
|
||||
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
|
||||
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
|
||||
|
||||
switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char.
|
||||
{
|
||||
case CPAttachmentCharacter:
|
||||
{
|
||||
var attributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:nil],
|
||||
view = [attributes objectForKey:_CPAttachmentView],
|
||||
viewSize = view ? view._frame.size : CGSizeMake(0, 0);
|
||||
|
||||
rangeWidth = prevRangeWidth + viewSize.width; // undo sizing of dummy character
|
||||
|
||||
isAttachment = YES;
|
||||
wrapRange = CPMakeRange(lineRange.location, lineRange.length - 1); // wrap before image
|
||||
|
||||
// prevent crash when image is larger than text container
|
||||
if (viewSize.width > containerSizeWidth)
|
||||
wrapRange.length++;
|
||||
|
||||
wrapWidth = rangeWidth;
|
||||
wrapRange._height = _lineHeight;
|
||||
wrapRange._base = _lineBase;
|
||||
|
||||
if (viewSize.height > _lineBase)
|
||||
_lineBase = viewSize.height;
|
||||
|
||||
if (viewSize.height > _lineHeight)
|
||||
_lineHeight = viewSize.height - descent + leading;
|
||||
|
||||
ascent = viewSize.height;
|
||||
break;
|
||||
}
|
||||
case 9: // '\t'
|
||||
{
|
||||
var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0];
|
||||
|
||||
isTabStop = YES;
|
||||
|
||||
if (nextTab)
|
||||
rangeWidth = nextTab._location - lineOrigin.x;
|
||||
else
|
||||
rangeWidth += 28; //FIXME
|
||||
} // fallthrough intentional
|
||||
case 32: // ' '
|
||||
wrapRange = CPMakeRangeCopy(lineRange);
|
||||
wrapWidth = rangeWidth;
|
||||
wrapRange._height = _lineHeight;
|
||||
wrapRange._base = _lineBase;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
case 13:
|
||||
isNewline = YES;
|
||||
}
|
||||
|
||||
advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent});
|
||||
prevRangeWidth = _lineWidth = rangeWidth;
|
||||
|
||||
if (lineOrigin.x + rangeWidth > containerSizeWidth)
|
||||
{
|
||||
if (wrapWidth)
|
||||
{
|
||||
lineRange = wrapRange;
|
||||
_lineWidth = wrapWidth;
|
||||
_lineHeight = wrapRange._height;
|
||||
_lineBase = wrapRange._base;
|
||||
}
|
||||
|
||||
isNewline = YES;
|
||||
isWordWrapped = YES;
|
||||
glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character
|
||||
}
|
||||
|
||||
if (isNewline || isTabStop || isAttachment)
|
||||
{
|
||||
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline])
|
||||
return;
|
||||
|
||||
if (isTabStop || isAttachment)
|
||||
lineOrigin.x += rangeWidth;
|
||||
|
||||
if (isNewline)
|
||||
{
|
||||
if (currentParagraphMinimumLineHeight && currentParagraphMinimumLineHeight > _lineHeight)
|
||||
_lineHeight = currentParagraphMinimumLineHeight;
|
||||
|
||||
if (currentParagraphMaximumLineHeight && currentParagraphMaximumLineHeight < _lineHeight)
|
||||
_lineHeight = currentParagraphMaximumLineHeight;
|
||||
|
||||
lineOrigin.y += _lineHeight;
|
||||
|
||||
if (currentParagraphLineSpacing)
|
||||
lineOrigin.y += currentParagraphLineSpacing;
|
||||
|
||||
if (lineOrigin.y > containerSizeHeight && _indexOfCurrentContainer < textContainersCount - 1)
|
||||
{
|
||||
_currentTextContainer = textContainers[++_indexOfCurrentContainer];
|
||||
containerSize = [_currentTextContainer containerSize];
|
||||
containerSizeWidth = containerSize.width;
|
||||
containerSizeHeight = containerSize.height;
|
||||
}
|
||||
|
||||
lineOrigin.x = 0;
|
||||
numLines++;
|
||||
isNewline = NO;
|
||||
_lineFragments = [];
|
||||
_lineHeight = 0;
|
||||
_lineBase = ascent;
|
||||
}
|
||||
|
||||
isTabStop = NO;
|
||||
isAttachment = NO;
|
||||
isWordWrapped = NO;
|
||||
_lineWidth = 0;
|
||||
advancements = [];
|
||||
currentAnchor = 0;
|
||||
prevRangeWidth = 0;
|
||||
lineRange = CPMakeRange(glyphIndex + 1, 0);
|
||||
measuringRange = CPMakeRange(glyphIndex + 1, 0);
|
||||
wrapRange = CPMakeRange(0, 0);
|
||||
wrapWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// this is to "flush" the remaining characters
|
||||
if (lineRange.length)
|
||||
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
|
||||
|
||||
var rect = CGRectMake(1, lineOrigin.y - descent, containerSizeWidth, currentFontLineHeight);
|
||||
[_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer];
|
||||
|
||||
var fragment = [_layoutManager._lineFragments lastObject];
|
||||
|
||||
if (fragment)
|
||||
fragment._isLast = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,793 @@
|
||||
/* RTFParser.j
|
||||
|
||||
Parse a RTF string into a CPAttributedString
|
||||
|
||||
Copyright (C) 2014 Daniel Boehringer
|
||||
|
||||
FIXME: this class should be redone using a 'real' parser
|
||||
|
||||
* all paragraph spacing information is currently not parsed
|
||||
|
||||
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPAttributedString.j>
|
||||
@import <Foundation/CPGeometry.j>
|
||||
@import "CPFontManager.j"
|
||||
@import "CPParagraphStyle.j"
|
||||
|
||||
@global CPLeftTextAlignment
|
||||
@global CPRightTextAlignment
|
||||
@global CPCenterTextAlignment
|
||||
@global CPJustifiedTextAlignment
|
||||
@global CPNaturalTextAlignment
|
||||
|
||||
@global CPFontAttributeName
|
||||
@global CPForegroundColorAttributeName
|
||||
|
||||
var hexTable = [];
|
||||
|
||||
// Hold the attributes of the current run
|
||||
@implementation _RTFAttribute : CPObject
|
||||
{
|
||||
CPRange _range;
|
||||
CPParagraphStyle paragraph;
|
||||
CPColor fgColour;
|
||||
CPColor bgColour;
|
||||
CPColor ulColour;
|
||||
CPString fontName;
|
||||
unsigned fontSize;
|
||||
BOOL bold;
|
||||
BOOL italic;
|
||||
BOOL underline;
|
||||
BOOL strikethrough;
|
||||
BOOL script;
|
||||
BOOL _tabChanged;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
[self resetFont];
|
||||
[self resetParagraphStyle];
|
||||
_range = CPMakeRange(0, 0);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var mynew = [_RTFAttribute new];
|
||||
|
||||
mynew.paragraph = [paragraph copy];
|
||||
mynew.fontName = fontName;
|
||||
mynew.fgColour = fgColour;
|
||||
mynew.bgColour = bgColour;
|
||||
mynew.ulColour = ulColour;
|
||||
|
||||
return mynew;
|
||||
}
|
||||
|
||||
- (CPFont)currentFont
|
||||
{
|
||||
var font = [CPFont _fontWithName:fontName size:fontSize bold:bold italic:italic];
|
||||
|
||||
if (font)
|
||||
return font;
|
||||
|
||||
//Before giving up and using a default font, we try if this is
|
||||
//not the case of a font with a composite name, such as
|
||||
//'Helvetica-Light'. In that case, even if we don't have
|
||||
//exactly an 'Helvetica-Light' font family, we might have an
|
||||
//'Helvetica' one.
|
||||
var range = [fontName rangeOfString:@"-"];
|
||||
|
||||
if (range.location != CPNotFound)
|
||||
{
|
||||
var fontFamily = [fontName substringToIndex: range.location];
|
||||
|
||||
font = [CPFont fontWithName:fontFamily size:fontSize];
|
||||
}
|
||||
|
||||
/* Last resort, default font. :-( */
|
||||
if (font == nil)
|
||||
font = [CPFont systemFontOfSize:fontSize];
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
- (CPNumber)script
|
||||
{
|
||||
return [CPNumber numberWithInt:script];
|
||||
}
|
||||
|
||||
- (CPNumber)underline
|
||||
{
|
||||
if (underline != 0)
|
||||
return [CPNumber numberWithInt:underline];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CPNumber)strikethrough
|
||||
{
|
||||
if (strikethrough != 0)
|
||||
return [CPNumber numberWithInt:strikethrough];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)resetParagraphStyle
|
||||
{
|
||||
paragraph = [[CPParagraphStyle defaultParagraphStyle] copy];
|
||||
}
|
||||
|
||||
- (void)resetFont
|
||||
{
|
||||
var font = [CPFont systemFontOfSize:12];
|
||||
|
||||
fontName = [font familyName];
|
||||
fontSize = 12.0;
|
||||
italic = NO;
|
||||
bold = NO;
|
||||
underline = 0;
|
||||
strikethrough = 0;
|
||||
script = 0;
|
||||
}
|
||||
|
||||
- (void)addTab:(float)location type:(CPTextTabType)type
|
||||
{
|
||||
var tab = [[CPTextTab alloc] initWithType:CPLeftTabStopType
|
||||
location:location];
|
||||
|
||||
if (!_tabChanged)
|
||||
{
|
||||
[paragraph setTabStops:[tab]];
|
||||
_tabChanged = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
[paragraph addTabStop: tab];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPDictionary)dictionary
|
||||
{
|
||||
var ret = @{};
|
||||
[ret setObject:[self currentFont] forKey:CPFontAttributeName];
|
||||
[ret setObject:paragraph forKey:CPParagraphStyleAttributeName];
|
||||
|
||||
if (fgColour)
|
||||
[ret setObject:fgColour forKey:CPForegroundColorAttributeName];
|
||||
|
||||
return ret;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
// based on https://github.com/lazygyu/RTF-parser
|
||||
|
||||
var kRTFParserType_char = 0,
|
||||
kRTFParserType_dest = 1,
|
||||
kRTFParserType_prop = 2,
|
||||
kRTFParserType_spec = 3;
|
||||
|
||||
// Keyword descriptions
|
||||
var kRgsymRtf = {
|
||||
// keyword dflt fPassDflt kwd idx
|
||||
"b" : [ "b", 1, false, kRTFParserType_prop, "propBold"],
|
||||
"ul" : [ "ul", 1, false, kRTFParserType_prop, "propUnderline"],
|
||||
"i" : [ "i", 1, false, kRTFParserType_prop, "propItalic"],
|
||||
"li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"],
|
||||
"pgnucltr" : [ "pgnucltr", "pgULtr", true, kRTFParserType_prop, "propPgnFormat"],
|
||||
"pgnlcltr" : [ "pgnlcltr", "pgLLtr", true, kRTFParserType_prop, "propPgnFormat"],
|
||||
"qc" : [ "qc", "justC", true, kRTFParserType_prop, "propJust"],
|
||||
"ql" : [ "ql", "justL", true, kRTFParserType_prop, "propJust"],
|
||||
"qr" : [ "qr", "justR", true, kRTFParserType_prop, "propJust"],
|
||||
"qj" : [ "qj", "justF", true, kRTFParserType_prop, "propJust"],
|
||||
"paperw" : [ "paperw", 12240, false, kRTFParserType_prop, "propXaPage"],
|
||||
"paperh" : [ "paperh", 15480, false, kRTFParserType_prop, "propYaPage"],
|
||||
"margl" : [ "margl", 1800, false, kRTFParserType_prop, "propXaLeft"],
|
||||
"margr" : [ "margr", 1800, false, kRTFParserType_prop, "propXaRight"],
|
||||
"margt" : [ "margt", 1440, false, kRTFParserType_prop, "propYaTop"],
|
||||
"margb" : [ "margb", 1440, false, kRTFParserType_prop, "propYaBottom"],
|
||||
"pgnstart" : [ "pgnstart", 1, true, kRTFParserType_prop, "propPgnStart"],
|
||||
"facingp" : [ "facingp", 1, true, kRTFParserType_prop, "propFacingp"],
|
||||
"landscape" : [ "landscape",1, true, kRTFParserType_prop, "propLandscape"],
|
||||
"par" : [ "par", 0, false, kRTFParserType_char, "\n"],
|
||||
"pard" : [ "pard", 0, false, kRTFParserType_prop, "propDefaultPara"],
|
||||
"\0x0a" : [ "\0x0a", 0, false, kRTFParserType_char, "\n"],
|
||||
"\0x0d" : [ "\0x0d", 0, false, kRTFParserType_char, ""],
|
||||
"tab" : [ "tab", 0, false, kRTFParserType_char, "\t"],
|
||||
"ldblquote" : [ "ldblquote",0, false, kRTFParserType_char, '"'],
|
||||
"rdblquote" : [ "rdblquote",0, false, kRTFParserType_char, '"'],
|
||||
"bin" : [ "bin", 0, false, kRTFParserType_spec, "ipfnBin"],
|
||||
"*" : [ "*", 0, false, kRTFParserType_spec, "ipfnDestSkip"],
|
||||
"'" : [ "'", 0, false, kRTFParserType_spec, "ipfnHex"],
|
||||
"author" : [ "author", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"buptim" : [ "buptim", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"colortbl" : [ "colortbl", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"comment" : [ "comment", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"creatim" : [ "creatim", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"doccomm" : [ "doccomm", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"fonttbl" : [ "fonttbl", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"footer" : [ "footer", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"footerf" : [ "footerf", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"footerl" : [ "footerl", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"footerr" : [ "footerr", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"footnote" : [ "footnote", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"ftncn" : [ "ftncn", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"ftnsep" : [ "ftnsep", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"ftnsepc" : [ "ftnsepc", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"fprq" : [ "fprq", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
// "fcharset" : [ "fcharset", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"rquote" : [ "rquote", 0, false, kRTFParserType_char, "'"],
|
||||
// "s" : [ "s", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"header" : [ "header", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"headerf" : [ "headerf", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"headerl" : [ "headerl", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"headerr" : [ "headerr", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"info" : [ "info", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"keywords" : [ "keywords", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"operator" : [ "operator", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"pict" : [ "pict", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"printim" : [ "printim", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"private1" : [ "private1", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"revtim" : [ "revtim", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"rxe" : [ "rxe", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"stylesheet" : [ "stylesheet",0, false, kRTFParserType_dest, "destSkip"],
|
||||
"subject" : [ "subject", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"tc" : [ "tc", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"title" : [ "title", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"txe" : [ "txe", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"xe" : [ "xe", 0, false, kRTFParserType_dest, "destSkip"],
|
||||
"[" : [ "[", 0, false, kRTFParserType_char, '['],
|
||||
" " : [ " ", 0, false, kRTFParserType_char, ' '],
|
||||
"]" : [ "]", 0, false, kRTFParserType_char, ']'],
|
||||
"{" : [ "{", 0, false, kRTFParserType_char, '{'],
|
||||
"}" : [ "}", 0, false, kRTFParserType_char, '}'],
|
||||
"\\" : [ "\\", 0, false, kRTFParserType_char, '\\']
|
||||
};
|
||||
|
||||
@implementation _CPRTFParser : CPObject
|
||||
{
|
||||
CPString _codePage;
|
||||
CGSize _paper;
|
||||
CPString _rtf;
|
||||
unsigned _curState;
|
||||
CPArray _states;
|
||||
unsigned _currentParseIndex;
|
||||
BOOL _hexreturn;
|
||||
_RTFAttribute _currentRun;
|
||||
CPAttributedString _result;
|
||||
CPArray _colorArray;
|
||||
CPArray _fontArray;
|
||||
CPString _freename;
|
||||
BOOL _parsingFontTable;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_paper = CPMakeSize(0, 0);
|
||||
_rtf = "";
|
||||
_curState = 0; // 0 = normal, 1 = skip
|
||||
_states = [];
|
||||
_currentParseIndex = 0;
|
||||
_hexreturn = NO;
|
||||
_result = [CPAttributedString new];
|
||||
_colorArray = [];
|
||||
_fontArray = ['Arial']; // FIXME: should be name of system font
|
||||
_freename = "";
|
||||
_parsingFontTable = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)_checkChar:(CPArray)sym parameter:(CPString)ch
|
||||
{
|
||||
switch (_curState)
|
||||
{
|
||||
case 0:
|
||||
if (sym && sym[4])
|
||||
return sym[4];
|
||||
|
||||
case 1:
|
||||
// CPLogConsole("skipped : " + sym[4]);
|
||||
return '';
|
||||
|
||||
default:
|
||||
if (sym && sym[4])
|
||||
return sym[4];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pushState
|
||||
{
|
||||
_states.push["group"];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)popState
|
||||
{
|
||||
_states.pop();
|
||||
|
||||
if (_curState > 0)
|
||||
_curState--;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPString)_parseSpec:(CPArray)sym parameter:(CPString)v
|
||||
{
|
||||
var ch = '';
|
||||
|
||||
switch (sym[4])
|
||||
{
|
||||
case "ipfnDestSkip":
|
||||
_curState++;
|
||||
return '';
|
||||
|
||||
case "ipfnHex":
|
||||
ch = _rtf.charAt(++_currentParseIndex);
|
||||
|
||||
var hex = '';
|
||||
|
||||
while (new RegExp("[a-fA-F0-9\\']").test(ch))
|
||||
{
|
||||
if (ch == "'")
|
||||
{
|
||||
_currentParseIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
hex += (ch + '');
|
||||
ch = _rtf.charAt(++_currentParseIndex);
|
||||
}
|
||||
//ch = parseInt(ch, 16);
|
||||
//console.log("hex : " + hex);
|
||||
_hexreturn = YES;
|
||||
_currentParseIndex--;
|
||||
|
||||
if (_curState !== 0)
|
||||
return '';
|
||||
else
|
||||
return hex;
|
||||
break;
|
||||
|
||||
case "codePage":
|
||||
ch = _rtf.charAt(++_currentParseIndex);
|
||||
|
||||
var code = '';
|
||||
|
||||
while (new RegExp("[0-9]").test(ch))
|
||||
{
|
||||
code += (ch + '');
|
||||
ch = _rtf.charAt(++_currentParseIndex);
|
||||
}
|
||||
|
||||
_codePage = code;
|
||||
_currentParseIndex--;
|
||||
break;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
- (void)_flushCurrentRun
|
||||
{
|
||||
var newOffset = 0;
|
||||
|
||||
if (_currentRun)
|
||||
{
|
||||
if ([_result length] == _currentRun._range.location)
|
||||
return;
|
||||
|
||||
_currentRun._range.length = [_result length] - _currentRun._range.location;
|
||||
newOffset = CPMaxRange(_currentRun._range);
|
||||
|
||||
var dict = [_currentRun dictionary];
|
||||
|
||||
[_result setAttributes:dict range:_currentRun._range]; // flush previous run
|
||||
_currentRun.fgColour = [CPColor blackColor];
|
||||
}
|
||||
else
|
||||
_currentRun = [_RTFAttribute new];
|
||||
|
||||
_currentRun._range = CPMakeRange(newOffset, 0); // open a new one
|
||||
}
|
||||
|
||||
- (CPString)_applyPropChange:sym parameter:param
|
||||
{
|
||||
//console.log("prop : " + sym[0] + " / param : " + param+ ' ');
|
||||
|
||||
switch (sym[0])
|
||||
{
|
||||
case "pard":
|
||||
[self _flushCurrentRun];
|
||||
break;
|
||||
|
||||
case "b": // bold
|
||||
if (param === 0)
|
||||
{
|
||||
if (_currentRun && _currentRun.bold)
|
||||
[self _flushCurrentRun];
|
||||
|
||||
_currentRun.bold = NO
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_currentRun && !_currentRun.bold)
|
||||
[self _flushCurrentRun];
|
||||
|
||||
_currentRun.bold = YES;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "i": // italic
|
||||
if (param === 0)
|
||||
{
|
||||
if (_currentRun && _currentRun.italic)
|
||||
[self _flushCurrentRun];
|
||||
|
||||
_currentRun.italic = NO
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_currentRun && !_currentRun.italic)
|
||||
[self _flushCurrentRun];
|
||||
|
||||
_currentRun.italic = YES;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "qc": // paragraph center
|
||||
[_currentRun.paragraph setAlignment:CPCenterTextAlignment];
|
||||
break;
|
||||
|
||||
case "paperw":
|
||||
_paper.width = param;
|
||||
break;
|
||||
|
||||
case "paperh":
|
||||
_paper.height = param;
|
||||
break;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
- (CPString)_changeDest:(CPArray)sym
|
||||
{
|
||||
switch (sym[0])
|
||||
{
|
||||
case "colortbl":
|
||||
_colorArray.push([CPColor blackColor]);
|
||||
break;
|
||||
|
||||
case "fonttbl":
|
||||
_parsingFontTable = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym[4] == "destSkip")
|
||||
{
|
||||
CPLogConsole("Dest skip start : [" + sym[0] + "]");
|
||||
_curState++;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
- (CPString)_translateKeyword:(CPString)keyword parameter:(CPString)param fParameter:(BOOL)fParam
|
||||
{
|
||||
if (kRgsymRtf[keyword] !== undefined)
|
||||
{
|
||||
var sym = kRgsymRtf[keyword];
|
||||
|
||||
switch (sym[3])
|
||||
{
|
||||
case kRTFParserType_prop:
|
||||
if (sym[2] || !fParam)
|
||||
param = sym[1];
|
||||
|
||||
return [self _applyPropChange:sym parameter:param];
|
||||
|
||||
case kRTFParserType_char:
|
||||
if((param + '') !== 'NaN' && (param + '') !== 'null' && (param + '').length)
|
||||
_currentParseIndex -= (param + '').length;
|
||||
|
||||
return [self _checkChar:sym parameter:param];
|
||||
|
||||
case kRTFParserType_dest:
|
||||
return [self _changeDest:sym];
|
||||
|
||||
case kRTFParserType_spec:
|
||||
return [self _parseSpec:sym parameter:param];
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (keyword)
|
||||
{
|
||||
case "red":
|
||||
var oldColor = [_colorArray lastObject],
|
||||
green = [oldColor greenComponent],
|
||||
blue = [oldColor blueComponent];
|
||||
|
||||
_colorArray.pop();
|
||||
_colorArray.push([CPColor colorWithRed:parseInt(param) / 255 green:green blue:blue alpha:1.0]);
|
||||
break;
|
||||
|
||||
case "green":
|
||||
var oldColor = [_colorArray lastObject],
|
||||
red = [oldColor redComponent],
|
||||
blue = [oldColor blueComponent];
|
||||
|
||||
_colorArray.pop();
|
||||
_colorArray.push([CPColor colorWithRed:red green: parseInt(param) / 255 blue:blue alpha:1.0]);
|
||||
break;
|
||||
|
||||
case "blue":
|
||||
var oldColor = [_colorArray lastObject],
|
||||
green = [oldColor greenComponent],
|
||||
red = [oldColor redComponent];
|
||||
|
||||
_colorArray.pop();
|
||||
_colorArray.push([CPColor colorWithRed:red green:green blue:parseInt(param) / 255 alpha:1.0]);
|
||||
_colorArray.push([CPColor blackColor]); // placeholder for next color
|
||||
break;
|
||||
|
||||
case "cf": // change foreground color
|
||||
[self _flushCurrentRun];
|
||||
var fontIndex = parseInt(param) - 1;
|
||||
|
||||
if (_currentRun && fontIndex >= 0)
|
||||
_currentRun.fgColour = _colorArray[fontIndex];
|
||||
|
||||
break;
|
||||
|
||||
case "f": // change font
|
||||
[self _flushCurrentRun];
|
||||
var fontIndex = parseInt(param);
|
||||
|
||||
if (_currentRun && fontIndex >= 0 && fontIndex < _fontArray.length)
|
||||
_currentRun.fontName = _fontArray[fontIndex];
|
||||
break;
|
||||
|
||||
case "fs": // change font size
|
||||
[self _flushCurrentRun];
|
||||
_currentRun.fontSize = parseInt(param) / 2;
|
||||
break;
|
||||
|
||||
case "tx": // tabstop
|
||||
var location = parseInt(param) / 20;
|
||||
|
||||
if (_currentRun)
|
||||
[_currentRun addTab:location type:CPLeftTabStopType];
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
CPLogConsole("skip : " + keyword + " param: " + param);
|
||||
|
||||
}
|
||||
|
||||
if (_states.length > 0)
|
||||
_curState = 1;
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
- (CPString)_parseKeyword:(CPString)rtf length:(unsigned)len
|
||||
{
|
||||
var ch = '',
|
||||
fParam = false,
|
||||
fNeg = false,
|
||||
keyword = '',
|
||||
param = '';
|
||||
|
||||
_rtf = rtf;
|
||||
|
||||
if (++_currentParseIndex >= len)
|
||||
return len;
|
||||
|
||||
ch = rtf.charAt(_currentParseIndex);
|
||||
|
||||
if (!/[a-zA-Z]/.test(ch))
|
||||
return [self _translateKeyword:ch parameter:nil fParameter:fParam];
|
||||
|
||||
while (new RegExp("[a-zA-Z]").test(ch))
|
||||
{
|
||||
keyword += ch;
|
||||
ch = rtf.charAt(++_currentParseIndex);
|
||||
}
|
||||
|
||||
if (ch == '-')
|
||||
{
|
||||
fNeg = true;
|
||||
ch = rtf.charAt(++_currentParseIndex);
|
||||
}
|
||||
|
||||
fParam = true;
|
||||
|
||||
while (new RegExp("[0-9]").test(ch))
|
||||
{
|
||||
param += (ch + '');
|
||||
ch = rtf.charAt(++_currentParseIndex);
|
||||
}
|
||||
|
||||
_currentParseIndex--;
|
||||
param = parseInt(param);
|
||||
|
||||
if (fNeg)
|
||||
param *= -1;
|
||||
|
||||
return [self _translateKeyword:keyword parameter:param fParameter:fParam];
|
||||
}
|
||||
|
||||
- (void)_appendPlainString:(CPString) aString
|
||||
{
|
||||
[_result replaceCharactersInRange:CPMakeRange([_result length], 0) withString:aString];
|
||||
|
||||
}
|
||||
- (CPAttributedString)parseRTF:(CPString)rtf
|
||||
{
|
||||
rtf = rtf.replace(/\\\n/g, "\\par\n");
|
||||
|
||||
if (rtf.length == 0)
|
||||
return '';
|
||||
|
||||
_currentParseIndex = -1;
|
||||
|
||||
var len = rtf.length,
|
||||
tmp = '',
|
||||
ch = '',
|
||||
hex = '',
|
||||
lastchar = 0;
|
||||
|
||||
while (_currentParseIndex < len)
|
||||
{
|
||||
tmp = rtf.charAt(++_currentParseIndex);
|
||||
|
||||
if (tmp !== "\\" && hex.length > 0)
|
||||
{
|
||||
[self _appendPlainString: String.fromCharCode(parseInt((hex), 16))];
|
||||
hex = '';
|
||||
}
|
||||
|
||||
switch (tmp)
|
||||
{
|
||||
case " ":
|
||||
if (lastchar == 1)
|
||||
{
|
||||
lastchar = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_freename += tmp;
|
||||
[self _appendPlainString:tmp];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "{":
|
||||
if ([self pushState])
|
||||
CPLogConsole("push");
|
||||
|
||||
break;
|
||||
|
||||
case "}":
|
||||
if ([self popState])
|
||||
CPLogConsole("pop");
|
||||
|
||||
if (_freename)
|
||||
{
|
||||
CPLogConsole(_freename);
|
||||
|
||||
if (_parsingFontTable)
|
||||
{
|
||||
_fontArray.push(_freename);
|
||||
_parsingFontTable = NO;
|
||||
}
|
||||
|
||||
_freename = "";
|
||||
}
|
||||
|
||||
[self _flushCurrentRun];
|
||||
break;
|
||||
|
||||
case "\\":
|
||||
_freename = '';
|
||||
ch = [self _parseKeyword:rtf length:len];
|
||||
|
||||
if (!_hexreturn && ch.length == 0)
|
||||
lastchar = 1;
|
||||
else
|
||||
lastchar = 0;
|
||||
|
||||
if (_hexreturn)
|
||||
{
|
||||
if (ch.length > 0)
|
||||
{
|
||||
if (parseInt(ch, 16) & 0x80)
|
||||
{
|
||||
hex += ch.toUpperCase();
|
||||
}
|
||||
else
|
||||
{
|
||||
[self _appendPlainString: String.fromCharCode(parseInt((hex + ch), 16))];
|
||||
hex = '';
|
||||
}
|
||||
|
||||
if (hex.length == 4)
|
||||
{
|
||||
var temp = parseInt(hex, 16);
|
||||
|
||||
if (hexTable && hexTable[hex.toUpperCase()] !== undefined)
|
||||
temp = parseInt(hexTable[hex.toUpperCase()], 16);
|
||||
|
||||
[self _appendPlainString: String.fromCharCode(temp)];
|
||||
hex = '';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CPLogConsole("hex skipped");
|
||||
}
|
||||
|
||||
_hexreturn = NO;
|
||||
}
|
||||
else if (ch !== undefined && _curState === 0)
|
||||
{
|
||||
[self _appendPlainString:ch];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 0x0d:
|
||||
case 0x0a:
|
||||
case '\n':
|
||||
case '\r':
|
||||
break;
|
||||
|
||||
default:
|
||||
lastchar = 0;
|
||||
|
||||
if (_curState == 0)
|
||||
[self _appendPlainString:tmp];
|
||||
else if (tmp !== ';')
|
||||
_freename += tmp;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _result;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
_CPRTFProducer.j
|
||||
|
||||
Serialize CPAttributedString to a RTF String
|
||||
|
||||
Copyright (C) 2014 Daniel Boehringer
|
||||
This file is based on the RTFProducer from GNUStep
|
||||
(which i co-authored with Fred Kiefer in 1999)
|
||||
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPAttributedString.j>
|
||||
@import "CPParagraphStyle.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPGraphics.j"
|
||||
@import "CPFontManager.j"
|
||||
|
||||
@global CPForegroundColorAttributeName
|
||||
@global CPBackgroundColorAttributeName
|
||||
@global CPUnderlineStyleAttributeName
|
||||
@global CPSuperscriptAttributeName
|
||||
@global CPBaselineOffsetAttributeName
|
||||
@global CPAttachmentAttributeName
|
||||
@global CPLigatureAttributeName
|
||||
@global CPKernAttributeName
|
||||
|
||||
@global CPLeftTextAlignment
|
||||
@global CPRightTextAlignment
|
||||
@global CPCenterTextAlignment
|
||||
@global CPJustifiedTextAlignment
|
||||
@global CPNaturalTextAlignment
|
||||
|
||||
var PAPERSIZE = @"PaperSize",
|
||||
LEFTMARGIN = @"LeftMargin",
|
||||
RIGHTMARGIN = @"RightMargin",
|
||||
TOPMARGIN = @"TopMargin",
|
||||
BUTTOMMARGIN = @"ButtomMargin";
|
||||
|
||||
function _points2twips(a) { return (a) * 20.0; }
|
||||
|
||||
@implementation _CPRTFProducer : CPObject
|
||||
{
|
||||
CPAttributedString text;
|
||||
CPMutableDictionary fontDict;
|
||||
CPMutableDictionary colorDict;
|
||||
CPDictionary docDict;
|
||||
CPMutableArray attachments;
|
||||
CPFont currentFont;
|
||||
CPColor fgColor;
|
||||
CPColor bgColor;
|
||||
CPColor ulColor;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
|
||||
+ (CPString)produceRTF:(CPAttributedString)aText documentAttributes:(CPDictionary)dict
|
||||
{
|
||||
var mynew = [self new];
|
||||
|
||||
return [mynew RTFDStringFromAttributedString:aText documentAttributes:dict];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark init methods
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
// maintain a dictionary for the used colours
|
||||
// (for rtf-header generation)
|
||||
colorDict = [CPMutableDictionary new];
|
||||
|
||||
//maintain a dictionary for the used fonts
|
||||
//(for rtf-header generation)
|
||||
fontDict = [CPMutableDictionary new];
|
||||
|
||||
fgColor = [CPColor blackColor];
|
||||
bgColor= [CPColor whiteColor];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// private stuff follows
|
||||
- (CPString)fontTable
|
||||
{
|
||||
if (![fontDict count])
|
||||
return @"";
|
||||
|
||||
var fontlistString = "",
|
||||
fontEnum,
|
||||
currFont,
|
||||
keyArray;
|
||||
|
||||
keyArray = [fontDict allKeys];
|
||||
keyArray = [keyArray sortedArrayUsingSelector:@selector(compare:)];
|
||||
fontEnum = [keyArray objectEnumerator];
|
||||
|
||||
while ((currFont = [fontEnum nextObject]) != nil)
|
||||
{
|
||||
var fontFamily,
|
||||
detail;
|
||||
|
||||
if ([currFont isEqualToString:@"Symbol"])
|
||||
fontFamily = @"tech";
|
||||
else if ([currFont isEqualToString:@"Helvetica"])
|
||||
fontFamily = @"swiss";
|
||||
else if ([currFont isEqualToString:@"Arial"])
|
||||
fontFamily = @"swiss";
|
||||
else if ([currFont isEqualToString:@"Courier"])
|
||||
fontFamily = @"modern";
|
||||
else if ([currFont isEqualToString:@"Times"])
|
||||
fontFamily = @"roman";
|
||||
else fontFamily = @"nil";
|
||||
|
||||
detail = [CPString stringWithFormat:@"%@\\f%@ %@;", [fontDict objectForKey:currFont], fontFamily, currFont];
|
||||
fontlistString += detail;
|
||||
}
|
||||
|
||||
return [CPString stringWithFormat:@"{\\fonttbl%@}\n", fontlistString];
|
||||
}
|
||||
|
||||
- (CPString)colorTable
|
||||
{
|
||||
if (![colorDict count])
|
||||
return @"";
|
||||
|
||||
var result,
|
||||
count = [colorDict count],
|
||||
list = [CPMutableArray arrayWithCapacity:count],
|
||||
keyEnum = [colorDict keyEnumerator],
|
||||
next,
|
||||
i;
|
||||
|
||||
while ((next = [keyEnum nextObject]) != nil)
|
||||
{
|
||||
var cn = [colorDict objectForKey:next];
|
||||
[list insertObject:[CPColor colorWithCSSString:next] atIndex:[cn intValue]-1];
|
||||
}
|
||||
|
||||
result = [CPString stringWithString:@"{\\colortbl;"];
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
var color = [[list objectAtIndex:i]
|
||||
colorUsingColorSpaceName:CPCalibratedRGBColorSpace];
|
||||
|
||||
result += [CPString stringWithFormat:@"\\red%d\\green%d\\blue%d;",
|
||||
([color redComponent] * 255),
|
||||
([color greenComponent] * 255),
|
||||
([color blueComponent] * 255)];
|
||||
}
|
||||
|
||||
result += @"}\n";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPString)documentAttributes
|
||||
{
|
||||
if (!docDict)
|
||||
return @"";
|
||||
|
||||
var result,
|
||||
detail,
|
||||
val,
|
||||
num;
|
||||
|
||||
result = [CPString string];
|
||||
|
||||
val = [docDict objectForKey:PAPERSIZE];
|
||||
|
||||
if (val)
|
||||
{
|
||||
detail = [CPString stringWithFormat:@"\\paperw%d \\paperh%d",
|
||||
_points2twips(val.width),
|
||||
_points2twips(val.height)];
|
||||
|
||||
result += detail;
|
||||
}
|
||||
|
||||
num = [docDict objectForKey:LEFTMARGIN];
|
||||
|
||||
if (num)
|
||||
{
|
||||
var f = [num floatValue];
|
||||
|
||||
detail = [CPString stringWithFormat:@"\\margl%d", _points2twips(f)];
|
||||
result += detail;
|
||||
}
|
||||
|
||||
num = [docDict objectForKey:RIGHTMARGIN];
|
||||
|
||||
if (num)
|
||||
{
|
||||
var f = [num floatValue];
|
||||
|
||||
detail = [CPString stringWithFormat:@"\\margr%d", _points2twips(f)];
|
||||
result += detail;
|
||||
}
|
||||
|
||||
num = [docDict objectForKey:TOPMARGIN];
|
||||
|
||||
if (num)
|
||||
{
|
||||
var f = [num floatValue];
|
||||
|
||||
detail = [CPString stringWithFormat:@"\\margt%d", _points2twips(f)];
|
||||
result += detail;
|
||||
}
|
||||
|
||||
num = [docDict objectForKey:BUTTOMMARGIN];
|
||||
|
||||
if (num)
|
||||
{
|
||||
var f = [num floatValue];
|
||||
|
||||
detail = [CPString stringWithFormat:@"\\margb%d", _points2twips(f)];
|
||||
result += detail;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPString)headerString
|
||||
{
|
||||
var result;
|
||||
|
||||
result = [CPString stringWithString:@"{\\rtf1\\ansi"];
|
||||
result += [self fontTable];
|
||||
result += [self colorTable];
|
||||
result += [self documentAttributes];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPString)trailerString
|
||||
{
|
||||
return @"}";
|
||||
}
|
||||
|
||||
- (CPString)fontToken:(CPString) fontName
|
||||
{
|
||||
var fCount = [fontDict objectForKey:fontName];
|
||||
|
||||
if (fCount == nil)
|
||||
{
|
||||
var count = [fontDict count];
|
||||
|
||||
fCount = [CPString stringWithFormat:@"\\f%d", count];
|
||||
[fontDict setObject:fCount forKey:fontName];
|
||||
}
|
||||
|
||||
return fCount;
|
||||
}
|
||||
|
||||
- (int)numberForColor:(CPColor)color
|
||||
{
|
||||
var num = [colorDict objectForKey:[color cssString]];
|
||||
|
||||
if (!num)
|
||||
[colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1]
|
||||
forKey:[color cssString]];
|
||||
|
||||
return [num intValue];
|
||||
}
|
||||
|
||||
- (CPString)paragraphStyle:(CPParagraphStyle)paraStyle
|
||||
{
|
||||
var headerString = [CPString stringWithString:@"\\pard"],
|
||||
twips;
|
||||
|
||||
if (paraStyle == nil)
|
||||
return headerString;
|
||||
|
||||
switch ([paraStyle alignment])
|
||||
{
|
||||
case CPRightTextAlignment:
|
||||
headerString += @"\\qr";
|
||||
break;
|
||||
|
||||
case CPCenterTextAlignment:
|
||||
headerString += @"\\qc";
|
||||
break;
|
||||
|
||||
case CPLeftTextAlignment:
|
||||
headerString += @"\\ql";
|
||||
break;
|
||||
|
||||
case CPJustifiedTextAlignment:
|
||||
headerString += @"\\qj";
|
||||
break;
|
||||
|
||||
default:
|
||||
headerString += @"\\ql";
|
||||
break;
|
||||
}
|
||||
|
||||
// write first line indent and left indent
|
||||
var twips = _points2twips([paraStyle firstLineHeadIndent]);
|
||||
|
||||
if (twips != 0.0)
|
||||
headerString += [CPString stringWithFormat:@"\\fi%d", twips];
|
||||
|
||||
twips = _points2twips([paraStyle headIndent]);
|
||||
|
||||
if (twips != 0.0)
|
||||
headerString += [CPString stringWithFormat:@"\\li%d", twips];
|
||||
|
||||
twips = _points2twips([paraStyle tailIndent]);
|
||||
|
||||
if (twips != 0.0)
|
||||
headerString += [CPString stringWithFormat:@"\\ri%d", twips];
|
||||
|
||||
twips = _points2twips([paraStyle paragraphSpacing]);
|
||||
|
||||
if (twips != 0.0)
|
||||
headerString += [CPString stringWithFormat:@"\\sa%d", twips];
|
||||
|
||||
twips = _points2twips([paraStyle minimumLineHeight]);
|
||||
|
||||
if (twips != 0.0)
|
||||
headerString += [CPString stringWithFormat:@"\\sl%d", twips];
|
||||
|
||||
twips = _points2twips([paraStyle maximumLineHeight]);
|
||||
|
||||
if (twips != 0.0)
|
||||
headerString += [CPString stringWithFormat:@"\\sl-%d", twips];
|
||||
|
||||
var enumerator,
|
||||
tab;
|
||||
|
||||
enumerator = [[paraStyle tabStops] objectEnumerator];
|
||||
|
||||
while ((tab = [enumerator nextObject]))
|
||||
{
|
||||
switch ([tab tabStopType])
|
||||
{
|
||||
case CPLeftTabStopType:
|
||||
// no tabkind emission needed
|
||||
break;
|
||||
/* case NSRightTabStopType:
|
||||
headerString += @"\\tqr";
|
||||
break;
|
||||
case NSCenterTabStopType:
|
||||
headerString += @"\\tqc";
|
||||
break;
|
||||
case NSDecimalTabStopType:
|
||||
headerString += @"\\tqdec";
|
||||
break;
|
||||
default:
|
||||
NSLog(@"Unknown tab stop type.");
|
||||
*/
|
||||
}
|
||||
|
||||
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
|
||||
}
|
||||
|
||||
return headerString;
|
||||
}
|
||||
|
||||
- (CPString)runStringForString:(CPString) substring
|
||||
attributes:(CPDictionary) attributes
|
||||
paragraphStart:(BOOL) first
|
||||
{
|
||||
var result = "",
|
||||
headerString = "",
|
||||
trailerString = "",
|
||||
attribEnum,
|
||||
currAttrib;
|
||||
|
||||
if (first)
|
||||
{
|
||||
var paraStyle = [attributes objectForKey:CPParagraphStyleAttributeName];
|
||||
headerString += [self paragraphStyle:paraStyle];
|
||||
}
|
||||
|
||||
/*
|
||||
* analyze attributes of current run
|
||||
*
|
||||
* FIXME: All the character attributes should be output relative to the font
|
||||
* attributes of the paragraph. So if the paragraph has underline on it should
|
||||
* still be possible to switch it off for some characters, which currently is
|
||||
* not possible.
|
||||
*/
|
||||
attribEnum = [attributes keyEnumerator];
|
||||
|
||||
while ((currAttrib = [attribEnum nextObject]) != nil)
|
||||
{
|
||||
if ([currAttrib isEqualToString:CPFontAttributeName])
|
||||
{
|
||||
/*
|
||||
* handle fonts
|
||||
*/
|
||||
var font,
|
||||
fontName,
|
||||
traits;
|
||||
|
||||
font = [attributes objectForKey:CPFontAttributeName];
|
||||
fontName = [font familyName];
|
||||
traits = [[CPFontManager sharedFontManager] traitsOfFont:font];
|
||||
|
||||
/*
|
||||
* font name
|
||||
*/
|
||||
if (currentFont == nil || ![fontName isEqualToString:[currentFont familyName]])
|
||||
headerString += [self fontToken:fontName];
|
||||
|
||||
/*
|
||||
* font size
|
||||
*/
|
||||
if (currentFont == nil || [font size] != [currentFont size])
|
||||
{
|
||||
var points = [font size] * 2,
|
||||
pString;
|
||||
|
||||
pString = [CPString stringWithFormat:@"\\fs%d", points];
|
||||
headerString += pString;
|
||||
}
|
||||
/*
|
||||
* font attributes
|
||||
*/
|
||||
if (traits & CPItalicFontMask)
|
||||
{
|
||||
headerString += @"\\i";
|
||||
trailerString += @"\\i0";
|
||||
}
|
||||
|
||||
if (traits & CPBoldFontMask)
|
||||
{
|
||||
headerString += @"\\b";
|
||||
trailerString += @"\\b0";
|
||||
}
|
||||
|
||||
if (first)
|
||||
currentFont = font;
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPForegroundColorAttributeName])
|
||||
{
|
||||
var color = [attributes objectForKey:CPForegroundColorAttributeName];
|
||||
|
||||
if (![color isEqual:fgColor])
|
||||
{
|
||||
headerString += [CPString stringWithFormat:@"\\cf%d", [self numberForColor:color]];
|
||||
trailerString += @"\\cf0";
|
||||
}
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPBackgroundColorAttributeName])
|
||||
{
|
||||
var color = [attributes objectForKey:CPBackgroundColorAttributeName];
|
||||
|
||||
if (![color isEqual:bgColor])
|
||||
{
|
||||
headerString += [CPString stringWithFormat:@"\\cb%d", [self numberForColor:color]];
|
||||
trailerString += @"\\cb0";
|
||||
}
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPUnderlineStyleAttributeName])
|
||||
{
|
||||
headerString += @"\\ul";
|
||||
trailerString += @"\\ulnone";
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPSuperscriptAttributeName])
|
||||
{
|
||||
var value = [attributes objectForKey:CPSuperscriptAttributeName],
|
||||
svalue = [value intValue] * 6;
|
||||
|
||||
if (svalue > 0)
|
||||
{
|
||||
headerString += [CPString stringWithFormat:@"\\up%d", svalue];
|
||||
trailerString += @"\\up0";
|
||||
}
|
||||
else if (svalue < 0)
|
||||
{
|
||||
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
|
||||
trailerString += @"\\dn0";
|
||||
}
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPBaselineOffsetAttributeName])
|
||||
{
|
||||
var value = [attributes objectForKey:CPBaselineOffsetAttributeName],
|
||||
svalue = [value floatValue] * 2;
|
||||
|
||||
if (svalue > 0)
|
||||
{
|
||||
headerString += [CPString stringWithFormat:@"\\up%d", svalue];
|
||||
trailerString += @"\\up0";
|
||||
}
|
||||
else if (svalue < 0)
|
||||
{
|
||||
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
|
||||
trailerString += @"\\dn0";
|
||||
}
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPAttachmentAttributeName])
|
||||
{
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPLigatureAttributeName])
|
||||
{
|
||||
}
|
||||
else if ([currAttrib isEqualToString:CPKernAttributeName])
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
substring = substring.replace(/\\/g, '\\\\');
|
||||
substring = substring.replace(/\n/g, '\\par\n');
|
||||
substring = substring.replace(/\t/g, '\\tab');
|
||||
substring = substring.replace(/{/g, '\\{');
|
||||
substring = substring.replace(/}/g, '\\}');
|
||||
// FIXME: All characters not in the standard encoding must be
|
||||
// replaced by \'xx
|
||||
|
||||
if (!first)
|
||||
{
|
||||
var braces;
|
||||
|
||||
if ([headerString length])
|
||||
braces = [CPString stringWithFormat:@"{%@ %@}", headerString, substring];
|
||||
else
|
||||
braces = substring;
|
||||
|
||||
result += braces;
|
||||
}
|
||||
else
|
||||
{
|
||||
var nobraces;
|
||||
|
||||
if ([headerString length])
|
||||
nobraces = [CPString stringWithFormat:@"%@ %@", headerString, substring];
|
||||
else
|
||||
nobraces = substring;
|
||||
|
||||
result += nobraces;
|
||||
}
|
||||
|
||||
return result + trailerString;
|
||||
}
|
||||
|
||||
- (CPString)bodyString
|
||||
{
|
||||
var string = [text string],
|
||||
result = "",
|
||||
loc = 0,
|
||||
length = [string length],
|
||||
currRange = CPMakeRange(loc, 0),
|
||||
completeRange = CPMakeRange(0, length),
|
||||
first = YES;
|
||||
|
||||
// FIXME <!> split along newline characters and run as outer loop
|
||||
while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs"
|
||||
{
|
||||
var attributes,
|
||||
substring,
|
||||
runString;
|
||||
|
||||
attributes = [text attributesAtIndex:CPMaxRange(currRange)
|
||||
longestEffectiveRange:currRange
|
||||
inRange:completeRange];
|
||||
substring = [string substringWithRange:currRange];
|
||||
runString = [self runStringForString:substring
|
||||
attributes:attributes
|
||||
paragraphStart:YES];
|
||||
|
||||
result += runString;
|
||||
first = NO;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
- (CPString)RTFDStringFromAttributedString:(CPAttributedString)aText
|
||||
documentAttributes:(CPDictionary)dict
|
||||
{
|
||||
var output = [CPString string],
|
||||
headerString,
|
||||
trailerString,
|
||||
bodyString;
|
||||
|
||||
text = aText;
|
||||
docDict = dict;
|
||||
|
||||
/*
|
||||
* do not change order! (esp. body has to be generated first; builds context)
|
||||
*/
|
||||
bodyString = [self bodyString];
|
||||
trailerString = [self trailerString];
|
||||
headerString = [self headerString];
|
||||
|
||||
output += headerString;
|
||||
output += bodyString;
|
||||
output += trailerString;
|
||||
return output;
|
||||
}
|
||||
@end
|
||||
+312
-43
@@ -24,9 +24,13 @@
|
||||
@import <Foundation/CPMutableArray.j>
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@class CPView
|
||||
@class _CPThemeAttribute
|
||||
@class CPImage
|
||||
@class CPColor
|
||||
@class CPApplication
|
||||
|
||||
var CPThemesByName = { },
|
||||
CPThemeDefaultTheme = nil,
|
||||
@@ -45,6 +49,8 @@ var CPThemesByName = { },
|
||||
+ (void)setDefaultTheme:(CPTheme)aTheme
|
||||
{
|
||||
CPThemeDefaultTheme = aTheme;
|
||||
|
||||
[CPFont initializeSystemFontFromTheme:aTheme];
|
||||
}
|
||||
|
||||
+ (CPTheme)defaultTheme
|
||||
@@ -256,7 +262,7 @@ var CPThemesByName = { },
|
||||
attributeNames = [attributes keyEnumerator],
|
||||
objectThemeClass = [anObject themeClass];
|
||||
|
||||
while ((attributeName = [attributeNames nextObject]) !== nil)
|
||||
while ((attributeName = [attributeNames nextObject]) != nil)
|
||||
[self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
|
||||
}
|
||||
|
||||
@@ -313,6 +319,245 @@ var CPThemeNameKey = @"CPThemeNameKey",
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CSS Theming
|
||||
|
||||
// The code below adds support for CSS theming with 100% compatibility with current theming system.
|
||||
// The idea is to extend CPColor and CPImage with CSS components and adapt low level UI components to
|
||||
// support this new kind of CPColor/CPImage. See CPImageView, CPView and _CPImageAndTextView.
|
||||
//
|
||||
// To be considered and treated as CSS based, a theme must set to YES the attribute "css-based" for CPView
|
||||
// in the theDescriptor (default value is NO), like in the example below :
|
||||
//
|
||||
// + (CPView)themedView
|
||||
// {
|
||||
// var view = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
||||
//
|
||||
// [self registerThemeValues:[[@"css-based", YES]] forView:view];
|
||||
//
|
||||
// return view;
|
||||
// }
|
||||
//
|
||||
// You can use the method -(BOOL)isCSSBased on a theme to determine how to cope with it in your own code.
|
||||
//
|
||||
// The method -(void)setCSSResourcesPath is meant to be used only by CPThemeBlend during theme loading in order to
|
||||
// replace the special path "%%" in CSS components (like in url(%%packed.png) ) with the path to the theme blend resources folder.
|
||||
//
|
||||
// DYNAMIC ATTRIBUTES SET:
|
||||
//
|
||||
// If the default theme is CSS based and exposes some dynamic attributes (like Aristo3 does for color schema), you can switch "live"
|
||||
// to another set of values by calling [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:aDynamicSet] where aDynamicSet is a
|
||||
// dictionary with keys being the names of exposed dynamic attributes (see the themeDescriptor.j of the CSS based theme you use)
|
||||
// and values, accordingly to the type of the corresponding default values provided by the theme (CPColor, a CSS color string, ...).
|
||||
// You can provide key-value pairs for a subset of the exposed dynamic attributes. If no key-value pair is specified for a dynamic
|
||||
// attribute, the theme default value will be used.
|
||||
//
|
||||
// To restore default theme values, simply pass nil as dynamic set : [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:nil]
|
||||
//
|
||||
// During theme loading, if your application responds to -(CPDictionary)themeDynamicAttributesSet, the returned values set is directly
|
||||
// applied to the default set. This avoids displaying the theme with its default values and then redisplaying it with your values.
|
||||
// The -(CPDictionary)themeDynamicAttributesSet must be declared in a CPApplication category (and not in your application delegate) as
|
||||
// it will be called during application initialisation, before the delegate has been defined.
|
||||
//
|
||||
// To use default theme values, simply return nil.
|
||||
//
|
||||
//
|
||||
// TYPICAL USAGE:
|
||||
//
|
||||
// One can imagine that a user can choose between some predefined color schemas. You can store this user preference in standard user defaults:
|
||||
//
|
||||
// [[CPUserDefaults standardUserDefaults] setObject:ConstantRepresentingAColorSchema forKey:@"userTheme"];
|
||||
// [[CPUserDefaults standardUserDefaults] synchronize];
|
||||
//
|
||||
// As you want to use the choosen color schema at run time, you've implemented -(CPDictionary)themeDynamicAttributesSet (see below), so updating
|
||||
// the UI as soon as the user has made his choice is easy :
|
||||
//
|
||||
// [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:[[CPApplication sharedApplication] themeDynamicAttributesSet]];
|
||||
//
|
||||
// If the user chooses the theme default color schema, you can then simply remove the user default:
|
||||
//
|
||||
// [[CPUserDefaults standardUserDefaults] removeObjectForKey:@"userTheme"];
|
||||
// [[CPUserDefaults standardUserDefaults] synchronize];
|
||||
//
|
||||
// Your CPApplication category could then be something like:
|
||||
//
|
||||
// @implementation CPApplication (themeColorSchema)
|
||||
//
|
||||
// - (CPDictionary)themeDynamicAttributesSet
|
||||
// {
|
||||
// var userTheme = [[CPUserDefaults standardUserDefaults] objectForKey:@"userTheme"];
|
||||
//
|
||||
// if (!userTheme)
|
||||
|
||||
// // Return nil to have the theme default color schema
|
||||
// return nil;
|
||||
//
|
||||
// switch (userTheme)
|
||||
// {
|
||||
// case ConstantRepresentingAColorSchema:
|
||||
// return @{
|
||||
// @"ExposedDynamicAttribute1": [CPColor colorWithRed:245.0/255.0 green:164.0/255.0 blue:9.0/255.0 alpha:0.85],
|
||||
// @"ExposedDynamicAttribute2": [CPColor colorWithRed:245.0/255.0 green:164.0/255.0 blue:9.0/255.0 alpha:0.85],
|
||||
// @"ExposedDynamicAttribute3": @"rgb(245,164,9)",
|
||||
// @"ExposedDynamicAttribute4": @"url(./Resources/mypacked.png)"
|
||||
// };
|
||||
// break;
|
||||
//
|
||||
// case AnotherConstantRepresentingAColorSchema:
|
||||
// return @{
|
||||
// @"ExposedDynamicAttribute1": [CPColor colorWithRed:39.0/255.0 green:216.0/255.0 blue:93.0/255.0 alpha:0.85],
|
||||
// @"ExposedDynamicAttribute3": @"rgb(39,216,93)"
|
||||
// };
|
||||
// break;
|
||||
//
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @end
|
||||
|
||||
var _savedThemesByName = { };
|
||||
|
||||
@implementation CPTheme (CSSTheming)
|
||||
|
||||
- (void)_initializeDynamicAttributesAndResourcesPathWithBundle:(CPBundle)aBundle
|
||||
{
|
||||
// First, save a template of the theme so dynamic attributes and images placeholders are preserved
|
||||
_savedThemesByName[_name] = [CPKeyedArchiver archivedDataWithRootObject:self];
|
||||
|
||||
// Then, compute the dynamic attributes set that will be applied to the theme
|
||||
// Does the application delegate provide a dynamic attributes set ?
|
||||
var dynamicSet = ([[CPApplication sharedApplication] respondsToSelector:@selector(themeDynamicAttributesSet)]) ? [[CPApplication sharedApplication] themeDynamicAttributesSet] : nil;
|
||||
|
||||
// Take the one defined in the theme
|
||||
var defaultDynamicSet = [self valueForAttributeWithName:@"dynamic-set" forClass:[CPView class]];
|
||||
|
||||
// Update the default dynamic attributes set with the one provided by the application
|
||||
[dynamicSet enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[defaultDynamicSet setObject:anObject forKey:aKey];
|
||||
}];
|
||||
|
||||
// Apply the resulting set
|
||||
[self applyDynamicSet:defaultDynamicSet];
|
||||
|
||||
// Then fix resources path in CSS directives
|
||||
[self setCSSResourcesPath:[aBundle resourcePath]];
|
||||
}
|
||||
|
||||
+ (void)updateDefaultThemeDynamicAttributesSetWithSet:(CPDictionary)dynamicSet
|
||||
{
|
||||
// First, get a copy of the theme template
|
||||
var newTheme = [CPKeyedUnarchiver unarchiveObjectWithData:_savedThemesByName[[CPThemeDefaultTheme name]]];
|
||||
|
||||
// Take the dynamic set defined in the theme
|
||||
var defaultDynamicSet = [newTheme valueForAttributeWithName:@"dynamic-set" forClass:[CPView class]];
|
||||
|
||||
// Update the default dynamic attributes set with the one provided by the application
|
||||
[dynamicSet enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[defaultDynamicSet setObject:anObject forKey:aKey];
|
||||
}];
|
||||
|
||||
// Apply the resulting set to the theme
|
||||
[newTheme applyDynamicSet:defaultDynamicSet];
|
||||
|
||||
// Then, fix resources path in CSS directives
|
||||
[newTheme setCSSResourcesPath:[[[[CPApplication sharedApplication] themeBlend] bundle] resourcePath]];
|
||||
|
||||
// Now apply the new theme variation
|
||||
[CPTheme setDefaultTheme:newTheme];
|
||||
|
||||
var windows = [[CPApplication sharedApplication] windows];
|
||||
|
||||
for (var i = 0, count = windows.length; i < count; i++)
|
||||
{
|
||||
[windows[i] _setThemeIncludingDescendants:newTheme];
|
||||
}
|
||||
|
||||
// Update the menu bar
|
||||
[CPMenu updateMenuBarAttributesWithTheme:newTheme];
|
||||
}
|
||||
|
||||
- (void)setCSSResourcesPath:(CPString)pathToResources
|
||||
{
|
||||
[_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[anObject enumerateKeysAndObjectsUsingBlock:function(aKey2, anObject2, stop2)
|
||||
{
|
||||
// anObject2 is now a _CPThemeAttribute
|
||||
|
||||
[[anObject2 values] enumerateKeysAndObjectsUsingBlock:function(aKey3, anObject3, stop3)
|
||||
{
|
||||
if (anObject3.isa && ([anObject3 isKindOfClass:CPImage] || [anObject3 isKindOfClass:CPColor]) && [anObject3 cssDictionary])
|
||||
{
|
||||
// We have a CSS defined image or color
|
||||
[self _fixPathInCSSDictionary:[anObject3 cssDictionary] withPathToResources:pathToResources];
|
||||
[self _fixPathInCSSDictionary:[anObject3 cssBeforeDictionary] withPathToResources:pathToResources];
|
||||
[self _fixPathInCSSDictionary:[anObject3 cssAfterDictionary] withPathToResources:pathToResources];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_fixPathInCSSDictionary:(CPDictionary)aDictionary withPathToResources:(CPString)pathToResources
|
||||
{
|
||||
[aDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[aDictionary setObject:[anObject stringByReplacingOccurrencesOfString:@"%%" withString:pathToResources] forKey:aKey];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)applyDynamicSet:(CPDictionary)dynamicSet
|
||||
{
|
||||
[_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[anObject enumerateKeysAndObjectsUsingBlock:function(aKey2, anObject2, stop2)
|
||||
{
|
||||
var anObject2Values = [anObject2 values];
|
||||
|
||||
[anObject2Values enumerateKeysAndObjectsUsingBlock:function(aKey3, anObject3, stop3)
|
||||
{
|
||||
if (anObject3.isa)
|
||||
{
|
||||
if (([anObject3 isKindOfClass:CPImage] || [anObject3 isKindOfClass:CPColor]) && [anObject3 cssDictionary])
|
||||
{
|
||||
// We have a CSS defined image or color
|
||||
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssDictionary]];
|
||||
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssBeforeDictionary]];
|
||||
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssAfterDictionary]];
|
||||
}
|
||||
else if ([anObject3 isKindOfClass:CPString])
|
||||
{
|
||||
// We have a direct string value
|
||||
if ([dynamicSet containsKey:anObject3])
|
||||
[anObject2Values setObject:[dynamicSet objectForKey:anObject3] forKey:aKey3];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_applyDynamicSet:(CPDictionary)dynamicSet toCSSDictionary:(CPDictionary)aDictionary
|
||||
{
|
||||
[aDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
if ([dynamicSet containsKey:anObject])
|
||||
[aDictionary setObject:[dynamicSet objectForKey:anObject] forKey:aKey];
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)isCSSBased
|
||||
{
|
||||
return !![self valueForAttributeWithName:@"css-based" forClass:[CPView class]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/*!
|
||||
* ThemeStates are immutable objects representing a particular ThemeState. Applications should never be creating
|
||||
* ThemeStates directly but should instead use the CPThemeState function.
|
||||
@@ -322,7 +567,7 @@ function ThemeState(stateNames)
|
||||
var stateNameKeys = [];
|
||||
this._stateNames = {};
|
||||
|
||||
for (key in stateNames)
|
||||
for (var key in stateNames)
|
||||
{
|
||||
if (!stateNames.hasOwnProperty(key))
|
||||
continue;
|
||||
@@ -569,6 +814,8 @@ CPThemeStateKeyWindow = CPThemeState("keyWindow");
|
||||
CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
|
||||
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
|
||||
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
|
||||
CPThemeStateAlternateState = CPThemeState("alternate");
|
||||
CPThemeStateComposedControl = CPThemeState("composed");
|
||||
|
||||
CPThemeStateNormalString = String(CPThemeStateNormal);
|
||||
|
||||
@@ -617,17 +864,12 @@ CPThemeStateNormalString = String(CPThemeStateNormal);
|
||||
|
||||
- (_CPThemeAttribute)attributeBySettingValue:(id)aValue
|
||||
{
|
||||
var attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute];
|
||||
|
||||
if (aValue !== undefined && aValue !== nil)
|
||||
attribute._values = @{ CPThemeStateNormalString: aValue };
|
||||
|
||||
return attribute;
|
||||
return [self attributeBySettingValue:aValue forState:CPThemeStateNormal];
|
||||
}
|
||||
|
||||
- (_CPThemeAttribute)attributeBySettingValue:(id)aValue forState:(ThemeState)aState
|
||||
{
|
||||
var shouldRemoveValue = aValue === undefined || aValue === nil,
|
||||
var shouldRemoveValue = aValue == nil,
|
||||
attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute],
|
||||
values = _values;
|
||||
|
||||
@@ -659,6 +901,7 @@ CPThemeStateNormalString = String(CPThemeStateNormal);
|
||||
|
||||
- (id)valueForState:(ThemeState)aState
|
||||
{
|
||||
// First, search in cache.
|
||||
var stateName = String(aState),
|
||||
value = _cache[stateName];
|
||||
|
||||
@@ -666,50 +909,76 @@ CPThemeStateNormalString = String(CPThemeStateNormal);
|
||||
if (value !== undefined)
|
||||
return value;
|
||||
|
||||
// Not in cache. OK, search in values.
|
||||
value = [_values objectForKey:stateName];
|
||||
|
||||
if (value === undefined || value === nil)
|
||||
if (value != nil)
|
||||
return _cache[stateName] = value;
|
||||
|
||||
// No direct match in values.
|
||||
// If this is a composite state, find the closest partial subset match.
|
||||
if (aState._stateNameCount > 1)
|
||||
{
|
||||
// If this is a composite state, find the closest partial subset match.
|
||||
if (aState._stateNameCount > 1)
|
||||
var largestThemeState = [self largestThemeStateMatchForState:aState returnedValue:@ref(value)];
|
||||
|
||||
if ((value !== undefined) && (value !== nil))
|
||||
return _cache[stateName] = value;
|
||||
}
|
||||
|
||||
// Still don't have a value? OK, let's use the normal value.
|
||||
value = [_values objectForKey:String(CPThemeStateNormal)];
|
||||
|
||||
if (value != nil)
|
||||
return _cache[stateName] = value;
|
||||
|
||||
// No normal value, try asking _themeDefaultAttribute
|
||||
value = [_themeDefaultAttribute valueForState:aState];
|
||||
|
||||
if (value != nil)
|
||||
return _cache[stateName] = value;
|
||||
|
||||
// Well, last option, use default value
|
||||
value = _defaultValue;
|
||||
|
||||
// Class theme attributes cannot use nil because it's a dictionary.
|
||||
// So transform CPNull into nil.
|
||||
if (value === [CPNull null])
|
||||
value = nil;
|
||||
|
||||
return _cache[stateName] = value;
|
||||
}
|
||||
|
||||
- (CPInteger)largestThemeStateMatchForState:(ThemeState)aState returnedValue:(id)valueRef
|
||||
{
|
||||
var stateName = String(aState),
|
||||
value,
|
||||
states = [_values allKeys],
|
||||
count = states ? states.length : 0,
|
||||
largestThemeState = 0;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var stateObject = CPThemeState(states[count]);
|
||||
|
||||
if (stateObject.isSubsetOf(aState) && stateObject._stateNameCount > largestThemeState)
|
||||
{
|
||||
var states = [_values allKeys],
|
||||
count = states ? states.length : 0,
|
||||
largestThemeState = 0;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var stateObject = CPThemeState(states[count]);
|
||||
|
||||
if (stateObject.isSubsetOf(aState) && stateObject._stateNameCount > largestThemeState)
|
||||
{
|
||||
value = [_values objectForKey:states[count]];
|
||||
largestThemeState = stateObject._stateNameCount;
|
||||
}
|
||||
}
|
||||
value = [_values objectForKey:states[count]];
|
||||
largestThemeState = stateObject._stateNameCount;
|
||||
}
|
||||
|
||||
// Still don't have a value? OK, let's use the normal value.
|
||||
if (value === undefined || value === nil)
|
||||
value = [_values objectForKey:String(CPThemeStateNormal)];
|
||||
}
|
||||
|
||||
if (value === undefined || value === nil)
|
||||
value = [_themeDefaultAttribute valueForState:aState];
|
||||
// _themeDefaultAttribute may have a larger theme state match. If so, we have to take it. If not, we take our closest match.
|
||||
var defaultAttributeFoundValue,
|
||||
defaultAttributeMatchLength = [_themeDefaultAttribute largestThemeStateMatchForState:aState returnedValue:@ref(defaultAttributeFoundValue)];
|
||||
|
||||
if (value === undefined || value === nil)
|
||||
if (defaultAttributeMatchLength > largestThemeState)
|
||||
{
|
||||
value = _defaultValue;
|
||||
|
||||
// Class theme attributes cannot use nil because it's a dictionary.
|
||||
// So transform CPNull into nil.
|
||||
if (value === [CPNull null])
|
||||
value = nil;
|
||||
value = defaultAttributeFoundValue;
|
||||
largestThemeState = defaultAttributeMatchLength;
|
||||
}
|
||||
|
||||
_cache[stateName] = value;
|
||||
|
||||
return value;
|
||||
@deref(valueRef) = value;
|
||||
return largestThemeState;
|
||||
}
|
||||
|
||||
- (_CPThemeAttribute)attributeBySettingParentAttribute:(_CPThemeAttribute)anAttribute
|
||||
@@ -769,7 +1038,7 @@ var ParentAttributeForCoder = nil;
|
||||
if ([aCoder containsValueForKey:@"state"])
|
||||
state = [aCoder decodeObjectForKey:@"state"];
|
||||
else
|
||||
state = CPThemeStateNormalString
|
||||
state = CPThemeStateNormalString;
|
||||
|
||||
[_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
|
||||
}
|
||||
|
||||
@@ -95,6 +95,17 @@
|
||||
[unarchiver finishDecoding];
|
||||
}
|
||||
|
||||
// CSS Theming
|
||||
|
||||
for (var i = 0, nb = [_themes count], allThemes = [self themeNames], aThemeName, aTheme; i < nb; i++)
|
||||
{
|
||||
aThemeName = allThemes[i];
|
||||
aTheme = [CPTheme themeNamed:aThemeName];
|
||||
|
||||
if ([aTheme isCSSBased])
|
||||
[aTheme _initializeDynamicAttributesAndResourcesPathWithBundle:aBundle];
|
||||
}
|
||||
|
||||
[_loadDelegate blendDidFinishLoading:self];
|
||||
}
|
||||
|
||||
|
||||
+21
-12
@@ -40,6 +40,7 @@
|
||||
@global CPTextFieldDidFocusNotification
|
||||
@global CPTextFieldDidBlurNotification
|
||||
|
||||
@global document
|
||||
|
||||
// TODO: should be conform to protocol CPTextFieldDelegate
|
||||
@protocol CPTokenFieldDelegate <CPObject>
|
||||
@@ -523,17 +524,25 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
var element = [self _inputElement];
|
||||
|
||||
CPTokenFieldInputResigning = YES;
|
||||
element.blur();
|
||||
|
||||
if (CPTokenFieldInputIsActive)
|
||||
element.blur();
|
||||
|
||||
if (!CPTokenFieldInputDidBlur)
|
||||
CPTokenFieldBlurHandler();
|
||||
|
||||
CPTokenFieldInputDidBlur = NO;
|
||||
CPTokenFieldInputResigning = NO;
|
||||
|
||||
if (element.parentNode == [_tokenScrollView documentView]._DOMElement)
|
||||
element.parentNode.removeChild(element);
|
||||
|
||||
// Previosly, we unflagged CPTokenFieldInputDidBlur and CPTokenFieldInputResigning before
|
||||
// the call to removeChild. This may result in DOM exceptions in Chrome under certain conditions.
|
||||
// See https://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node
|
||||
// for why we need to unflag CPTokenFieldInputDidBlur and CPTokenFieldInputResigning
|
||||
// only after removing the element.
|
||||
|
||||
CPTokenFieldInputDidBlur = NO;
|
||||
CPTokenFieldInputResigning = NO;
|
||||
|
||||
CPTokenFieldInputIsActive = NO;
|
||||
|
||||
if (document.attachEvent)
|
||||
@@ -541,7 +550,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
CPTokenFieldCachedSelectStartFunction = nil;
|
||||
CPTokenFieldCachedDragFunction = nil;
|
||||
|
||||
document.body.ondrag = CPTokenFieldCachedDragFunction
|
||||
document.body.ondrag = CPTokenFieldCachedDragFunction;
|
||||
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction
|
||||
}
|
||||
|
||||
@@ -622,7 +631,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
if (aValue !== nil && ![aValue isKindOfClass:[CPArray class]])
|
||||
if (aValue != nil && ![aValue isKindOfClass:[CPArray class]])
|
||||
{
|
||||
[super setObjectValue:nil];
|
||||
return;
|
||||
@@ -637,7 +646,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
newTokens = [];
|
||||
|
||||
// Preserve as many existing tokens as possible to reduce redraw flickering.
|
||||
if (aValue !== nil)
|
||||
if (aValue != nil)
|
||||
{
|
||||
for (var i = 0, count = [aValue count]; i < count; i++)
|
||||
{
|
||||
@@ -658,7 +667,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (newToken === nil)
|
||||
if (newToken == nil)
|
||||
{
|
||||
newToken = [_CPTokenFieldToken new];
|
||||
[newToken setTokenField:self];
|
||||
@@ -1239,7 +1248,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
var scrollToToken = _shouldScrollTo;
|
||||
|
||||
if (scrollToToken === CPScrollDestinationLeft)
|
||||
scrollToToken = tokens[_selectedRange.location]
|
||||
scrollToToken = tokens[_selectedRange.location];
|
||||
else if (scrollToToken === CPScrollDestinationRight)
|
||||
scrollToToken = tokens[MAX(0, CPMaxRange(_selectedRange) - 1)];
|
||||
[self _scrollTokenViewToVisible:scrollToToken];
|
||||
@@ -1302,7 +1311,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
{
|
||||
var stringForRepresentedObject = [_tokenFieldDelegate tokenField:self displayStringForRepresentedObject:representedObject];
|
||||
|
||||
if (stringForRepresentedObject !== nil)
|
||||
if (stringForRepresentedObject != nil)
|
||||
return stringForRepresentedObject;
|
||||
}
|
||||
|
||||
@@ -1324,7 +1333,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
{
|
||||
var approvedObjects = [_tokenFieldDelegate tokenField:self shouldAddObjects:tokens atIndex:index];
|
||||
|
||||
if (approvedObjects !== nil)
|
||||
if (approvedObjects != nil)
|
||||
return approvedObjects;
|
||||
}
|
||||
|
||||
@@ -1346,7 +1355,7 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
{
|
||||
var token = [_tokenFieldDelegate tokenField:self representedObjectForEditingString:aString];
|
||||
|
||||
if (token !== nil && token !== undefined)
|
||||
if (token != nil)
|
||||
return token;
|
||||
// If nil was returned, assume the string is the represented object. The alternative would have been
|
||||
// to not add anything to the object value array for a nil response.
|
||||
|
||||
+3
-3
@@ -153,7 +153,7 @@ var CPToolbarsByIdentifier = nil,
|
||||
|
||||
if (!toolbarsSharingIdentifier)
|
||||
{
|
||||
toolbarsSharingIdentifier = []
|
||||
toolbarsSharingIdentifier = [];
|
||||
[CPToolbarsByIdentifier setObject:toolbarsSharingIdentifier forKey:identifier];
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ var CPToolbarsByIdentifier = nil,
|
||||
|
||||
item = [item copy];
|
||||
|
||||
if (item === nil)
|
||||
if (item == nil)
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier \"" + identifier + "\""];
|
||||
|
||||
@@ -588,7 +588,7 @@ var CPToolbarIdentifierKey = @"CPToolbarIdentifierKey",
|
||||
[aCoder encodeBool:_showsBaselineSeparator forKey:CPToolbarShowsBaselineSeparatorKey];
|
||||
[aCoder encodeBool:_allowsUserCustomization forKey:CPToolbarAllowsUserCustomizationKey];
|
||||
[aCoder encodeBool:_isVisible forKey:CPToolbarIsVisibleKey];
|
||||
[aCoder encodeInt:_sizeMode forKey:CPToolbarSizeModeKey]
|
||||
[aCoder encodeInt:_sizeMode forKey:CPToolbarSizeModeKey];
|
||||
|
||||
[aCoder encodeObject:_identifiedItems forKey:CPToolbarIdentifiedItemsKey];
|
||||
[aCoder encodeObject:_defaultItems forKey:CPToolbarDefaultItemsKey];
|
||||
|
||||
+17
-8
@@ -51,7 +51,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
|
||||
|
||||
A CPTrackingArea defines a region of view that generates mouse-tracking and
|
||||
cursor-update events when the mouse is over that region.
|
||||
*/
|
||||
@@ -61,7 +61,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
|
||||
CPTrackingAreaOptions _options @accessors(getter=options);
|
||||
id _owner @accessors(getter=owner);
|
||||
CPDictionary _userInfo @accessors(getter=userInfo);
|
||||
|
||||
|
||||
CPView _referencingView @accessors(property=view);
|
||||
CGRect _windowRect @accessors(getter=windowRect);
|
||||
|
||||
@@ -72,13 +72,13 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly
|
||||
/*!
|
||||
Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly
|
||||
all these events.
|
||||
*/
|
||||
- (CPTrackingArea)initWithRect:(CGRect)aRect options:(CPTrackingAreaOptions)options owner:(id)owner userInfo:(CPDictionary)userInfo
|
||||
{
|
||||
if (owner === nil)
|
||||
if (owner == nil)
|
||||
[CPException raise:CPInternalInconsistencyException reason:"No owner specified"];
|
||||
|
||||
if (options === 0)
|
||||
@@ -116,7 +116,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
|
||||
if ([_owner respondsToSelector:@selector(cursorUpdate:)])
|
||||
_implementedOwnerMethods |= CPTrackingOwnerImplementsCursorUpdate;
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,16 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
|
||||
|
||||
- (void)_updateWindowRect
|
||||
{
|
||||
_windowRect = [_referencingView convertRect:((_options & CPTrackingInVisibleRect) ? [_referencingView visibleRect] : _viewRect) toView:[[_referencingView window] _windowView]];
|
||||
[self _updateWindowRectWithReferencingSuperViewVisibleRect:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
To speed up things we allow the caller to pass the visible rect of the referencing view's superview.
|
||||
This allows the visible rect calculations to be optimized when traveling down the view hierarchy.
|
||||
*/
|
||||
- (void)_updateWindowRectWithReferencingSuperViewVisibleRect:(CGRect)referencingSuperviewVisibleRect
|
||||
{
|
||||
_windowRect = [_referencingView convertRect:((_options & CPTrackingInVisibleRect) ? [_referencingView _visibleRectWithSuperviewVisibleRect:referencingSuperviewVisibleRect] : _viewRect) toView:[[_referencingView window] _windowView]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -147,7 +156,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
|
||||
_referencingView = [aCoder decodeObjectForKey:CPTrackingAreaReferencingViewKey];
|
||||
_windowRect = [aCoder decodeObjectForKey:CPTrackingAreaWindowRect];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,31 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPIndexPath)indexPath
|
||||
{
|
||||
if (_parentNode != nil)
|
||||
{
|
||||
var path;
|
||||
var index;
|
||||
|
||||
index = [[_parentNode childNodes] indexOfObject:self];
|
||||
path = [_parentNode indexPath];
|
||||
|
||||
if (path != nil)
|
||||
{
|
||||
return [path indexPathByAddingIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return [CPIndexPath indexPathWithIndex:index];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
return [_childNodes count] <= 0;
|
||||
|
||||
@@ -160,13 +160,13 @@ var CPUserDefaultsControllerSharedKey = "CPUserDefaultsControllerSharedKey";
|
||||
- (id)valueForKey:(CPString)aKey
|
||||
{
|
||||
var value = [_cachedValues objectForKey:aKey];
|
||||
if (value === nil)
|
||||
if (value == nil)
|
||||
{
|
||||
value = [[_controller defaults] objectForKey:aKey];
|
||||
if (value === nil)
|
||||
if (value == nil)
|
||||
value = [[_controller initialValues] objectForKey:aKey];
|
||||
|
||||
if (value !== nil)
|
||||
if (value != nil)
|
||||
[_cachedValues setObject:value forKey:aKey];
|
||||
}
|
||||
return value;
|
||||
@@ -220,7 +220,7 @@ var CPUserDefaultsControllerSharedKey = "CPUserDefaultsControllerSharedKey";
|
||||
[self willChangeValueForKey:key];
|
||||
|
||||
var initialValue = [initial objectForKey:key];
|
||||
if (initialValue !== nil)
|
||||
if (initialValue != nil)
|
||||
[_cachedValues setObject:initialValue forKey:key];
|
||||
else
|
||||
[_cachedValues removeObjectForKey:key];
|
||||
|
||||
+320
-169
@@ -112,6 +112,11 @@ CPViewHeightSizable = 16;
|
||||
*/
|
||||
CPViewMaxYMargin = 32;
|
||||
|
||||
_CPViewWillAppearNotification = @"CPViewWillAppearNotification";
|
||||
_CPViewDidAppearNotification = @"CPViewDidAppearNotification";
|
||||
_CPViewWillDisappearNotification = @"CPViewWillDisappearNotification";
|
||||
_CPViewDidDisappearNotification = @"CPViewDidDisappearNotification";
|
||||
|
||||
CPViewBoundsDidChangeNotification = @"CPViewBoundsDidChangeNotification";
|
||||
CPViewFrameDidChangeNotification = @"CPViewFrameDidChangeNotification";
|
||||
|
||||
@@ -124,7 +129,8 @@ var DOMElementPrototype = nil,
|
||||
BackgroundVerticalThreePartImage = 1,
|
||||
BackgroundHorizontalThreePartImage = 2,
|
||||
BackgroundNinePartImage = 3,
|
||||
BackgroundTransparentColor = 4;
|
||||
BackgroundTransparentColor = 4,
|
||||
BackgroundCSSStyling = 5;
|
||||
#endif
|
||||
|
||||
var CPViewFlags = { },
|
||||
@@ -172,6 +178,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPArray _registeredDraggedTypesArray;
|
||||
|
||||
BOOL _isHidden;
|
||||
BOOL _isHiddenOrHasHiddenAncestor;
|
||||
BOOL _hitTests;
|
||||
BOOL _clipsToBounds;
|
||||
|
||||
@@ -189,6 +196,10 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPArray _DOMImageSizes;
|
||||
|
||||
unsigned _backgroundType;
|
||||
|
||||
// CSS styling
|
||||
CPArray _cssStylePreviousState;
|
||||
DOMElement _cssStyleNode;
|
||||
#endif
|
||||
|
||||
CGRect _dirtyRect;
|
||||
@@ -239,10 +250,14 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
BOOL _allowsVibrancy @accessors(property=allowsVibrancy);
|
||||
CPAppearance _appearance @accessors(getter=appearance);
|
||||
CPAppearance _effectiveAppearance;
|
||||
CPAppearance _currentAppearance;
|
||||
|
||||
CPMutableArray _trackingAreas @accessors(getter=trackingAreas, copy);
|
||||
BOOL _inhibitUpdateTrackingAreas;
|
||||
|
||||
id _animator;
|
||||
CPDictionary _animationsDictionary;
|
||||
BOOL _inhibitDOMUpdates @accessors(setter=_setInhibitDOMUpdates);
|
||||
BOOL _forceUpdates @accessors(setter=_setForceUpdates);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -309,6 +324,20 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"view";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"css-based": NO,
|
||||
@"dynamic-set": [CPNull null],
|
||||
@"nib2cib-adjustment-frame": CGRectMakeZero()
|
||||
};
|
||||
}
|
||||
|
||||
- (void)_setupViewFlags
|
||||
{
|
||||
var theClass = [self class],
|
||||
@@ -369,6 +398,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
_opacity = 1.0;
|
||||
_isHidden = NO;
|
||||
_isHiddenOrHasHiddenAncestor = NO;
|
||||
_hitTests = YES;
|
||||
|
||||
_hierarchyScaleSize = CGSizeMake(1.0 , 1.0);
|
||||
@@ -387,16 +417,31 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
_DOMImageParts = [];
|
||||
_DOMImageSizes = [];
|
||||
|
||||
_cssStylePreviousState = @[];
|
||||
#endif
|
||||
|
||||
_animator = nil;
|
||||
_animationsDictionary = @{};
|
||||
|
||||
// Set the current appearance to something that can't be the correct one so it will recalculate it at the first layout.
|
||||
_currentAppearance = aFrame;
|
||||
|
||||
[self _setupViewFlags];
|
||||
[self _loadThemeAttributes];
|
||||
}
|
||||
|
||||
_inhibitDOMUpdates = NO;
|
||||
_forceUpdates = NO;
|
||||
|
||||
// We force here an updateTrackingAreas in order to let the view install its own tracking areas
|
||||
// in the case where an externally owned tracking area is installed on this view before putting
|
||||
// it in the view hierarchy.
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Sets the tooltip for the receiver.
|
||||
|
||||
@@ -404,7 +449,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setToolTip:(CPString)aToolTip
|
||||
{
|
||||
if (_toolTip == aToolTip)
|
||||
if (_toolTip === aToolTip)
|
||||
return;
|
||||
|
||||
if (aToolTip && ![aToolTip isKindOfClass:CPString])
|
||||
@@ -432,7 +477,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
return;
|
||||
|
||||
if (!_toolTipFunctionIn)
|
||||
_toolTipFunctionIn = function(e) { [_CPToolTip scheduleToolTipForView:self]; }
|
||||
_toolTipFunctionIn = function(e) { [_CPToolTip scheduleToolTipForView:self]; };
|
||||
|
||||
if (!_toolTipFunctionOut)
|
||||
_toolTipFunctionOut = function(e) { [_CPToolTip invalidateCurrentToolTipIfNeeded]; };
|
||||
@@ -562,7 +607,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self window] _dirtyKeyViewLoop];
|
||||
|
||||
// If this is already one of our subviews, remove it.
|
||||
if (aSubview._superview == self)
|
||||
if (aSubview._superview === self)
|
||||
{
|
||||
var index = [_subviews indexOfObjectIdenticalTo:aSubview];
|
||||
|
||||
@@ -591,8 +636,9 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
// Remove the view from its previous superview.
|
||||
[aSubview _removeFromSuperview];
|
||||
|
||||
[aSubview _postViewWillAppearNotification];
|
||||
// Set ourselves as the superview.
|
||||
aSubview._superview = self;
|
||||
[aSubview _setSuperview:self];
|
||||
}
|
||||
|
||||
if (anIndex === CPNotFound || anIndex >= count)
|
||||
@@ -617,11 +663,6 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[aSubview setNextResponder:self];
|
||||
[aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]];
|
||||
|
||||
// If the subview is not hidden and one of its ancestors is hidden,
|
||||
// notify the subview that it is now hidden.
|
||||
if (![aSubview isHidden] && [self isHiddenOrHasHiddenAncestor])
|
||||
[aSubview _notifyViewDidHide];
|
||||
|
||||
[aSubview viewDidMoveToSuperview];
|
||||
|
||||
// Set the subview's window to our own.
|
||||
@@ -682,6 +723,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self window] _dirtyKeyViewLoop];
|
||||
|
||||
[_superview willRemoveSubview:self];
|
||||
[self _postViewWillDisappearNotification];
|
||||
|
||||
[_superview._subviews removeObjectIdenticalTo:self];
|
||||
|
||||
@@ -691,13 +733,10 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
// If the view is not hidden and one of its ancestors is hidden,
|
||||
// notify the view that it is now unhidden.
|
||||
if (!_isHidden && [_superview isHiddenOrHasHiddenAncestor])
|
||||
[self _notifyViewDidUnhide];
|
||||
[self _setSuperview:nil];
|
||||
|
||||
[self _notifyWindowDidResignKey];
|
||||
[self _notifyViewDidResignFirstResponder];
|
||||
|
||||
_superview = nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -763,7 +802,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
var addedSubview = nil,
|
||||
addedSubviewEnumerator = [addedSubviews objectEnumerator];
|
||||
|
||||
while ((addedSubview = [addedSubviewEnumerator nextObject]) !== nil)
|
||||
while ((addedSubview = [addedSubviewEnumerator nextObject]) != nil)
|
||||
[self addSubview:addedSubview];
|
||||
|
||||
// If the order is fine, no need to reorder.
|
||||
@@ -857,7 +896,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
do
|
||||
{
|
||||
if (view == aView)
|
||||
if (view === aView)
|
||||
return YES;
|
||||
} while(view = [view superview])
|
||||
|
||||
@@ -972,7 +1011,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
- (CPView)viewWithTag:(CPInteger)aTag
|
||||
{
|
||||
if ([self tag] == aTag)
|
||||
if ([self tag] === aTag)
|
||||
return self;
|
||||
|
||||
var index = 0,
|
||||
@@ -1007,14 +1046,26 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setFrame:(CGRect)aFrame
|
||||
{
|
||||
if (CGRectEqualToRect(_frame, aFrame))
|
||||
if (CGRectEqualToRect(_frame, aFrame) && !_forceUpdates)
|
||||
return;
|
||||
|
||||
// Save the previous value as we can be traveling down the view hierarchy.
|
||||
// If view is not connected with a window don't update tracking areas.
|
||||
var saveInhibitUpdateTrackingAreas = _window ? _window._inhibitUpdateTrackingAreas : YES;
|
||||
|
||||
// We only want to update tracking areas once so turn it off for all views.
|
||||
if (_window)
|
||||
_window._inhibitUpdateTrackingAreas = YES;
|
||||
|
||||
// Turn off change notifications for only this view
|
||||
_inhibitFrameAndBoundsChangedNotifications = YES;
|
||||
|
||||
[self setFrameOrigin:aFrame.origin];
|
||||
[self setFrameSize:aFrame.size];
|
||||
|
||||
if (_window)
|
||||
_window._inhibitUpdateTrackingAreas = saveInhibitUpdateTrackingAreas;
|
||||
|
||||
_inhibitFrameAndBoundsChangedNotifications = NO;
|
||||
|
||||
if (_postsFrameChangedNotifications)
|
||||
@@ -1023,8 +1074,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
if (_isSuperviewAClipView)
|
||||
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas)
|
||||
[self _updateTrackingAreas];
|
||||
if (!saveInhibitUpdateTrackingAreas)
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1078,7 +1129,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
var origin = _frame.origin;
|
||||
|
||||
if (!aPoint || CGPointEqualToPoint(origin, aPoint))
|
||||
if (!aPoint || (CGPointEqualToPoint(origin, aPoint) && !_forceUpdates))
|
||||
return;
|
||||
|
||||
origin.x = aPoint.x;
|
||||
@@ -1091,13 +1142,16 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
var transform = _superview ? _superview._boundsTransform : NULL;
|
||||
if (!_inhibitDOMUpdates)
|
||||
{
|
||||
var transform = _superview ? _superview._boundsTransform : NULL;
|
||||
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, transform, origin.x, origin.y);
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, transform, origin.x, origin.y);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1110,7 +1164,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
var size = _frame.size;
|
||||
|
||||
if (!aSize || CGSizeEqualToSize(size, aSize))
|
||||
if (!aSize || (CGSizeEqualToSize(size, aSize) && !_forceUpdates))
|
||||
return;
|
||||
|
||||
var oldSize = CGSizeMakeCopy(size);
|
||||
@@ -1250,8 +1304,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreasWithRecursion:!_autoresizesSubviews];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1264,7 +1318,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
#if PLATFORM(DOM)
|
||||
var scale = [self scaleSize];
|
||||
|
||||
CPDOMDisplayServerSetStyleSize(_DOMElement, aSize.width * 1 / scale.width, aSize.height * 1 / scale.height);
|
||||
if (!_inhibitDOMUpdates)
|
||||
CPDOMDisplayServerSetStyleSize(_DOMElement, aSize.width * 1 / scale.width, aSize.height * 1 / scale.height);
|
||||
|
||||
if (_DOMContentsElement)
|
||||
{
|
||||
@@ -1299,8 +1354,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
if (_isSuperviewAClipView)
|
||||
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas)
|
||||
[self _updateTrackingAreas];
|
||||
if (_window && !_window._inhibitUpdateTrackingAreas)
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1367,8 +1422,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1411,8 +1466,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
|
||||
@@ -1424,7 +1479,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
var mask = [self autoresizingMask];
|
||||
|
||||
if (mask == CPViewNotSizable)
|
||||
if ((mask === CPViewNotSizable) || !_superview)
|
||||
return;
|
||||
|
||||
var frame = _superview._frame,
|
||||
@@ -1593,10 +1648,8 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
// FIXME: Should we return to visibility? This breaks in FireFox, Opera, and IE.
|
||||
// _DOMElement.style.visibility = (_isHidden = aFlag) ? "hidden" : "visible";
|
||||
_isHidden = aFlag;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMElement.style.display = _isHidden ? "none" : "block";
|
||||
_DOMElement.style.display = aFlag ? "none" : "block";
|
||||
#endif
|
||||
|
||||
if (aFlag)
|
||||
@@ -1607,7 +1660,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
do
|
||||
{
|
||||
if (self == view)
|
||||
if (self === view)
|
||||
{
|
||||
[_window makeFirstResponder:[self nextValidKeyView]];
|
||||
break;
|
||||
@@ -1616,33 +1669,89 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
while (view = [view superview]);
|
||||
}
|
||||
|
||||
[self _notifyViewDidHide];
|
||||
[self _postViewWillDisappearNotification];
|
||||
[self _recursiveGainedHiddenAncestor];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
[self _notifyViewDidUnhide];
|
||||
|
||||
[self _postViewWillAppearNotification];
|
||||
[self _recursiveLostHiddenAncestor];
|
||||
}
|
||||
|
||||
_isHidden = aFlag;
|
||||
}
|
||||
|
||||
- (void)_notifyViewDidHide
|
||||
- (void)_postViewWillAppearNotification
|
||||
{
|
||||
[self viewDidHide];
|
||||
|
||||
var count = [_subviews count];
|
||||
|
||||
while (count--)
|
||||
[_subviews[count] _notifyViewDidHide];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillAppearNotification object:self userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)_notifyViewDidUnhide
|
||||
- (void)_postViewDidAppearNotification
|
||||
{
|
||||
[self viewDidUnhide];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidAppearNotification object:self userInfo:nil];
|
||||
}
|
||||
|
||||
var count = [_subviews count];
|
||||
- (void)_postViewWillDisappearNotification
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillDisappearNotification object:self userInfo:nil];
|
||||
}
|
||||
|
||||
while (count--)
|
||||
[_subviews[count] _notifyViewDidUnhide];
|
||||
- (void)_postViewDidDisappearNotification
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidDisappearNotification object:self userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)_setSuperview:(CPView)aSuperview
|
||||
{
|
||||
var hasOldSuperview = (_superview != nil),
|
||||
hasNewSuperview = (aSuperview != nil),
|
||||
oldSuperviewIsHidden = hasOldSuperview && [_superview isHiddenOrHasHiddenAncestor],
|
||||
newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor];
|
||||
|
||||
if (!newSuperviewIsHidden && oldSuperviewIsHidden)
|
||||
[self _recursiveLostHiddenAncestor];
|
||||
|
||||
if (newSuperviewIsHidden && !oldSuperviewIsHidden)
|
||||
[self _recursiveGainedHiddenAncestor];
|
||||
|
||||
_superview = aSuperview;
|
||||
|
||||
if (hasOldSuperview)
|
||||
[self _postViewDidDisappearNotification];
|
||||
|
||||
if (hasNewSuperview)
|
||||
[self _postViewDidAppearNotification];
|
||||
}
|
||||
|
||||
- (void)_recursiveLostHiddenAncestor
|
||||
{
|
||||
if (_isHiddenOrHasHiddenAncestor)
|
||||
{
|
||||
_isHiddenOrHasHiddenAncestor = NO;
|
||||
[self viewDidUnhide];
|
||||
}
|
||||
|
||||
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
|
||||
{
|
||||
[view _recursiveLostHiddenAncestor];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_recursiveGainedHiddenAncestor
|
||||
{
|
||||
if (!_isHidden)
|
||||
{
|
||||
[self viewDidHide];
|
||||
}
|
||||
|
||||
_isHiddenOrHasHiddenAncestor = YES;
|
||||
|
||||
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
|
||||
{
|
||||
[view _recursiveGainedHiddenAncestor];
|
||||
}];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1677,7 +1786,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setAlphaValue:(float)anAlphaValue
|
||||
{
|
||||
if (_opacity == anAlphaValue)
|
||||
if (_opacity === anAlphaValue)
|
||||
return;
|
||||
|
||||
_opacity = anAlphaValue;
|
||||
@@ -1712,12 +1821,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (BOOL)isHiddenOrHasHiddenAncestor
|
||||
{
|
||||
var view = self;
|
||||
|
||||
while (view && ![view isHidden])
|
||||
view = [view superview];
|
||||
|
||||
return view !== nil;
|
||||
return _isHiddenOrHasHiddenAncestor;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1881,24 +1985,34 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
if (_backgroundColor == aColor)
|
||||
if (_backgroundColor === aColor)
|
||||
return;
|
||||
|
||||
if (aColor == [CPNull null])
|
||||
if (aColor === [CPNull null])
|
||||
aColor = nil;
|
||||
|
||||
_backgroundColor = aColor;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (_backgroundType === BackgroundCSSStyling)
|
||||
[CPColor restorePreviousCSSState:@ref(_cssStylePreviousState) forDOMElement:_DOMElement];
|
||||
|
||||
var patternImage = [_backgroundColor patternImage],
|
||||
colorExists = _backgroundColor && ([_backgroundColor patternImage] || [_backgroundColor alphaComponent] > 0.0),
|
||||
colorHasAlpha = colorExists && [_backgroundColor alphaComponent] < 1.0,
|
||||
supportsRGBA = CPFeatureIsCompatible(CPCSSRGBAFeature),
|
||||
colorNeedsDOMElement = colorHasAlpha && !supportsRGBA,
|
||||
amount = 0,
|
||||
slices;
|
||||
slices,
|
||||
// For CSS theming
|
||||
isCSSBasedColor = [_backgroundColor isCSSBased];
|
||||
|
||||
if ([patternImage isThreePartImage])
|
||||
if (isCSSBasedColor)
|
||||
{
|
||||
_backgroundType = BackgroundCSSStyling;
|
||||
amount = -_DOMImageParts.length;
|
||||
}
|
||||
else if ([patternImage isThreePartImage])
|
||||
{
|
||||
_backgroundType = [patternImage isVertical] ? BackgroundVerticalThreePartImage : BackgroundHorizontalThreePartImage;
|
||||
amount = 3;
|
||||
@@ -1927,7 +2041,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
var image = slices[i],
|
||||
size = [image size];
|
||||
|
||||
if (!size || (size.width == 0 && size.height == 0))
|
||||
if (!size || (size.width === 0 && size.height === 0))
|
||||
size = nil;
|
||||
|
||||
_DOMImageSizes[i] = size;
|
||||
@@ -1961,7 +2075,14 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
_DOMElement.removeChild(_DOMImageParts.pop());
|
||||
}
|
||||
|
||||
if (_backgroundType === BackgroundTrivialColor || _backgroundType === BackgroundTransparentColor)
|
||||
if (_backgroundType === BackgroundCSSStyling)
|
||||
{
|
||||
_cssStyleNode = [_backgroundColor applyCSSColorForView:self
|
||||
onDOMElement:_DOMElement
|
||||
styleNode:_cssStyleNode
|
||||
previousState:@ref(_cssStylePreviousState)];
|
||||
}
|
||||
else if (_backgroundType === BackgroundTrivialColor || _backgroundType === BackgroundTransparentColor)
|
||||
{
|
||||
var colorCSS = colorExists ? [_backgroundColor cssString] : "";
|
||||
|
||||
@@ -1982,10 +2103,12 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[0], size.width, size.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
_DOMElement.style.background = colorCSS;
|
||||
|
||||
if (patternImage)
|
||||
CPDOMDisplayServerSetStyleBackgroundSize(_DOMElement, [patternImage size].width + "px", [patternImage size].height + "px");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2019,7 +2142,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
partIndex++;
|
||||
}
|
||||
|
||||
if (_backgroundType == BackgroundNinePartImage)
|
||||
if (_backgroundType === BackgroundNinePartImage)
|
||||
{
|
||||
var left = _DOMImageSizes[0] ? _DOMImageSizes[0].width : 0,
|
||||
right = _DOMImageSizes[2] ? _DOMImageSizes[2].width : 0,
|
||||
@@ -2039,6 +2162,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMImageParts[partIndex], NULL, left, 0.0);
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], width, _DOMImageSizes[1].height);
|
||||
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-x";
|
||||
partIndex++;
|
||||
}
|
||||
if (_DOMImageSizes[2])
|
||||
@@ -2050,6 +2174,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMImageParts[partIndex], NULL, 0.0, top);
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], _DOMImageSizes[3].width, height);
|
||||
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-y";
|
||||
partIndex++;
|
||||
}
|
||||
if (_DOMImageSizes[4])
|
||||
@@ -2062,6 +2187,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
CPDOMDisplayServerSetStyleRightTop(_DOMImageParts[partIndex], NULL, 0.0, top);
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], _DOMImageSizes[5].width, height);
|
||||
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-y";
|
||||
partIndex++;
|
||||
}
|
||||
if (_DOMImageSizes[6])
|
||||
@@ -2073,6 +2199,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
CPDOMDisplayServerSetStyleLeftBottom(_DOMImageParts[partIndex], NULL, left, 0.0);
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], width, _DOMImageSizes[7].height);
|
||||
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-x";
|
||||
partIndex++;
|
||||
}
|
||||
if (_DOMImageSizes[8])
|
||||
@@ -2080,7 +2207,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPDOMDisplayServerSetStyleRightBottom(_DOMImageParts[partIndex], NULL, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
else if (_backgroundType == BackgroundVerticalThreePartImage)
|
||||
else if (_backgroundType === BackgroundVerticalThreePartImage)
|
||||
{
|
||||
var top = _DOMImageSizes[0] ? _DOMImageSizes[0].height : 0,
|
||||
bottom = _DOMImageSizes[2] ? _DOMImageSizes[2].height : 0;
|
||||
@@ -2112,7 +2239,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], frameSize.width, bottom);
|
||||
}
|
||||
}
|
||||
else if (_backgroundType == BackgroundHorizontalThreePartImage)
|
||||
else if (_backgroundType === BackgroundHorizontalThreePartImage)
|
||||
{
|
||||
var left = _DOMImageSizes[0] ? _DOMImageSizes[0].width : 0,
|
||||
right = _DOMImageSizes[2] ? _DOMImageSizes[2].width : 0;
|
||||
@@ -2716,7 +2843,7 @@ setBoundsOrigin:
|
||||
*/
|
||||
- (void)viewDidLayout
|
||||
{
|
||||
[self _recomputeAppearance];
|
||||
[self _recomputeAppearanceWithSuperviewEffectiveAppearance:[self effectiveAppearance]];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
@@ -2736,11 +2863,16 @@ setBoundsOrigin:
|
||||
Returns the rectangle of the receiver not clipped by its superview.
|
||||
*/
|
||||
- (CGRect)visibleRect
|
||||
{
|
||||
return [self _visibleRectWithSuperviewVisibleRect:nil];
|
||||
}
|
||||
|
||||
- (CGRect)_visibleRectWithSuperviewVisibleRect:(CGRect)superviewVisibleRect
|
||||
{
|
||||
if (!_superview)
|
||||
return _bounds;
|
||||
|
||||
return CGRectIntersection([self convertRect:[_superview visibleRect] fromView:_superview], _bounds);
|
||||
return CGRectIntersection([self convertRect:superviewVisibleRect || [_superview _visibleRectWithSuperviewVisibleRect:nil] fromView:_superview], _bounds);
|
||||
}
|
||||
|
||||
// Scrolling
|
||||
@@ -2851,8 +2983,8 @@ setBoundsOrigin:
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME Not yet implemented
|
||||
/*!
|
||||
Scrolls the view’s CPClipView in the direction of a mouse event that occurs outside of it.
|
||||
*/
|
||||
- (BOOL)autoscroll:(CPEvent)anEvent
|
||||
{
|
||||
@@ -2917,7 +3049,7 @@ setBoundsOrigin:
|
||||
*/
|
||||
- (BOOL)inLiveResize
|
||||
{
|
||||
return _inLiveResize;
|
||||
return [self window] && [[self window] _inLiveResize];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -3074,7 +3206,7 @@ setBoundsOrigin:
|
||||
*/
|
||||
- (void)setLayer:(CALayer)aLayer
|
||||
{
|
||||
if (_layer == aLayer)
|
||||
if (_layer === aLayer)
|
||||
return;
|
||||
|
||||
if (_layer)
|
||||
@@ -3368,6 +3500,22 @@ setBoundsOrigin:
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPView (CSSTheming)
|
||||
|
||||
- (void)setDOMClassName:(CPString)aClassName
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMElement.className = aClassName;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)isCSSBased
|
||||
{
|
||||
return [[CPTheme defaultTheme] valueForAttributeWithName:@"css-based" forClass:CPView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPView (Appearance)
|
||||
|
||||
@@ -3393,53 +3541,59 @@ setBoundsOrigin:
|
||||
[self setNeedsLayout:YES];
|
||||
}
|
||||
|
||||
var CPAppearanceAqua = [CPAppearance appearanceNamed:CPAppearanceNameAqua];
|
||||
var CPAppearanceLightContent = [CPAppearance appearanceNamed:CPAppearanceNameLightContent];
|
||||
var CPAppearanceVibrantLight = [CPAppearance appearanceNamed:CPAppearanceNameVibrantLight];
|
||||
var CPAppearanceVibrantDark = [CPAppearance appearanceNamed:CPAppearanceNameVibrantDark];
|
||||
|
||||
/*! @ignore
|
||||
*/
|
||||
- (void)_recomputeAppearance
|
||||
- (void)_recomputeAppearanceWithSuperviewEffectiveAppearance:(CPAppearance)superviewEffectiveAppearance
|
||||
{
|
||||
var effectiveAppearance = [self effectiveAppearance];
|
||||
var effectiveAppearance = _appearance || superviewEffectiveAppearance;
|
||||
|
||||
if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameAqua]])
|
||||
{
|
||||
[self setThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
}
|
||||
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameLightContent]])
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self setThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
}
|
||||
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]])
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self setThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
}
|
||||
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark]])
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self setThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
// Only reset the themestates if it is different from the current one so we get good performance.
|
||||
if (effectiveAppearance != _currentAppearance) {
|
||||
switch (effectiveAppearance) {
|
||||
case CPAppearanceAqua:
|
||||
[self setThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
break;
|
||||
case CPAppearanceLightContent:
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self setThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
break;
|
||||
case CPAppearanceVibrantLight:
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self setThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
break;
|
||||
case CPAppearanceVibrantDark:
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self setThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
break;
|
||||
default:
|
||||
[self unsetThemeState:CPThemeStateAppearanceAqua];
|
||||
[self unsetThemeState:CPThemeStateAppearanceLightContent];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
|
||||
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
|
||||
}
|
||||
|
||||
_currentAppearance = effectiveAppearance;
|
||||
}
|
||||
|
||||
// var start = [CPDate new];
|
||||
|
||||
for (var i = 0, size = [_subviews count]; i < size; i++)
|
||||
{
|
||||
[[_subviews objectAtIndex:i] _recomputeAppearance];
|
||||
[[_subviews objectAtIndex:i] _recomputeAppearanceWithSuperviewEffectiveAppearance:effectiveAppearance];
|
||||
}
|
||||
// [_subviews makeObjectsPerformSelector:@selector(_recomputeAppearance)];
|
||||
|
||||
@@ -3487,15 +3641,16 @@ setBoundsOrigin:
|
||||
/*!
|
||||
Invoked automatically when the view’s geometry changes such that its tracking areas need to be recalculated.
|
||||
|
||||
You should override this method to remove out of date tracking areas and add recomputed tracking areas;
|
||||
|
||||
You should override this method to remove out of date tracking areas, add recomputed tracking areas and then call super;
|
||||
|
||||
Cocoa calls this on every view, whereas they have tracking area(s) or not.
|
||||
Cappuccino behaves differently :
|
||||
- updateTrackingAreas is called when placing a view in the view hierarchy (that is in a window)
|
||||
- updateTrackingAreas is called during initWithFrame
|
||||
- updateTrackingAreas is also called when placing a view in the view hierarchy (that is in a window)
|
||||
- if you have only CPTrackingInVisibleRect tracking areas attached to a view, it will not be called again (until you move the view in the hierarchy)
|
||||
- if you have at least one non-CPTrackingInVisibleRect tracking area attached, it will be called every time the view geometry could be modified
|
||||
You don't have to touch to CPTrackingInVisibleRect tracking areas, they will be automatically updated
|
||||
|
||||
|
||||
Please note that it is the owner of a tracking area who is called for updateTrackingAreas.
|
||||
But, if a view without any tracking area is inserted in the view hierarchy (that is, in a window), the view is called for updateTrackingAreas.
|
||||
This enables you to use updateTrackingArea to initially attach your tracking areas to the view.
|
||||
@@ -3505,25 +3660,6 @@ setBoundsOrigin:
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
This utility method is intended for CPView subclasses overriding updateTrackingAreas
|
||||
|
||||
Typical use would be :
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[self removeAllTrackingAreas];
|
||||
|
||||
... add your specific updated tracking areas ...
|
||||
}
|
||||
|
||||
*/
|
||||
- (void)removeAllTrackingAreas
|
||||
{
|
||||
while (_trackingAreas.length > 0)
|
||||
[self _removeTrackingArea:_trackingAreas[0]];
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
- (void)_removeTrackingArea:(CPTrackingArea)trackingArea
|
||||
@@ -3535,24 +3671,35 @@ setBoundsOrigin:
|
||||
[_trackingAreas removeObjectIdenticalTo:trackingArea];
|
||||
}
|
||||
|
||||
- (void)_updateTrackingAreas
|
||||
- (void)_updateTrackingAreasWithRecursion:(BOOL)shouldCallRecursively
|
||||
{
|
||||
_inhibitUpdateTrackingAreas = YES;
|
||||
|
||||
[self _recursivelyUpdateTrackingAreas];
|
||||
|
||||
_inhibitUpdateTrackingAreas = NO;
|
||||
[self _updateTrackingAreasWithRecursion:shouldCallRecursively withReferencingSuperViewVisibleRect:[_superview visibleRect]];
|
||||
}
|
||||
|
||||
- (void)_recursivelyUpdateTrackingAreas
|
||||
/*!
|
||||
The referencingSuperViewVisibleRect is used to speed up the execution of this as the visibleRect method is a heavy operation.
|
||||
It has to go up the view hierarchy and get every superviews visibleRect to transform and intersect them together.
|
||||
Here we keep the superviews visible rect when we are going down the view hierarchy to make the operation much faster.
|
||||
*/
|
||||
- (void)_updateTrackingAreasWithRecursion:(BOOL)shouldCallRecursively withReferencingSuperViewVisibleRect:(CGRect)referencingSuperViewVisibleRect
|
||||
{
|
||||
[self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwners]];
|
||||
[self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwnersWithReferencingSuperViewVisibleRect:referencingSuperViewVisibleRect]];
|
||||
|
||||
for (var i = 0; i < _subviews.length; i++)
|
||||
[_subviews[i] _recursivelyUpdateTrackingAreas];
|
||||
if (shouldCallRecursively)
|
||||
{
|
||||
// Now, call _updateTrackingAreasWithRecursion on subviews
|
||||
|
||||
for (var i = 0; i < _subviews.length; i++)
|
||||
[_subviews[i] _updateTrackingAreasWithRecursion:YES withReferencingSuperViewVisibleRect:[self _visibleRectWithSuperviewVisibleRect:referencingSuperViewVisibleRect]];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPArray)_calcTrackingAreaOwners
|
||||
{
|
||||
return [self _calcTrackingAreaOwnersWithReferencingSuperViewVisibleRect:nil];
|
||||
}
|
||||
|
||||
- (CPArray)_calcTrackingAreaOwnersWithReferencingSuperViewVisibleRect:(CGRect)referencingSuperViewVisibleRect
|
||||
{
|
||||
// First search all owners that must be notified
|
||||
// Remark: 99.99% of time, the only owner will be the view itself
|
||||
@@ -3565,7 +3712,7 @@ setBoundsOrigin:
|
||||
var trackingArea = _trackingAreas[i];
|
||||
|
||||
if ([trackingArea options] & CPTrackingInVisibleRect)
|
||||
[trackingArea _updateWindowRect];
|
||||
[trackingArea _updateWindowRectWithReferencingSuperViewVisibleRect:referencingSuperViewVisibleRect];
|
||||
|
||||
else
|
||||
{
|
||||
@@ -3630,26 +3777,29 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
// Also decode these "early".
|
||||
_frame = [aCoder decodeRectForKey:CPViewFrameKey];
|
||||
_bounds = [aCoder decodeRectForKey:CPViewBoundsKey];
|
||||
_scaleSize = [aCoder containsValueForKey:CPViewScaleKey] ? [aCoder decodeSizeForKey:CPViewScaleKey] : CGSizeMake(1.0, 1.0);
|
||||
_hierarchyScaleSize = [aCoder containsValueForKey:CPViewSizeScaleKey] ? [aCoder decodeSizeForKey:CPViewSizeScaleKey] : CGSizeMake(1.0, 1.0);
|
||||
_isScaled = [aCoder containsValueForKey:CPViewIsScaledKey] ? [aCoder decodeBoolForKey:CPViewIsScaledKey] : NO;
|
||||
_subviews = @[];
|
||||
|
||||
// Trying to fix "not ready" views
|
||||
_trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey] || @[];
|
||||
|
||||
[self _decodeThemeObjectsWithCoder:aCoder];
|
||||
[self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]];
|
||||
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey];
|
||||
|
||||
if (!_trackingAreas)
|
||||
_trackingAreas = [];
|
||||
|
||||
// We have to manually check because it may be 0, so we can't use ||
|
||||
_tag = [aCoder containsValueForKey:CPViewTagKey] ? [aCoder decodeIntForKey:CPViewTagKey] : -1;
|
||||
_identifier = [aCoder decodeObjectForKey:CPReuseIdentifierKey];
|
||||
|
||||
_window = [aCoder decodeObjectForKey:CPViewWindowKey];
|
||||
_superview = [aCoder decodeObjectForKey:CPViewSuperviewKey];
|
||||
|
||||
// We have to manually add the subviews so that they will receive
|
||||
// viewWillMoveToSuperview: and viewDidMoveToSuperview:
|
||||
_subviews = [];
|
||||
|
||||
var subviews = [aCoder decodeObjectForKey:CPViewSubviewsKey] || [];
|
||||
|
||||
@@ -3666,7 +3816,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
|
||||
// Other views (CPBox) might set an autoresizes mask on their subviews before it is actually decoded.
|
||||
// We make sure we don't override the value by checking if it was already set.
|
||||
if (_autoresizingMask === nil)
|
||||
if (_autoresizingMask == nil)
|
||||
_autoresizingMask = [aCoder decodeIntForKey:CPViewAutoresizingMaskKey] || CPViewNotSizable;
|
||||
|
||||
_autoresizesSubviews = ![aCoder containsValueForKey:CPViewAutoresizesSubviewsKey] || [aCoder decodeBoolForKey:CPViewAutoresizesSubviewsKey];
|
||||
@@ -3678,12 +3828,10 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
if (_toolTip)
|
||||
[self _installToolTipEventHandlers];
|
||||
|
||||
_scaleSize = [aCoder containsValueForKey:CPViewScaleKey] ? [aCoder decodeSizeForKey:CPViewScaleKey] : CGSizeMake(1.0, 1.0);
|
||||
_hierarchyScaleSize = [aCoder containsValueForKey:CPViewSizeScaleKey] ? [aCoder decodeSizeForKey:CPViewSizeScaleKey] : CGSizeMake(1.0, 1.0);
|
||||
_isScaled = [aCoder containsValueForKey:CPViewIsScaledKey] ? [aCoder decodeBoolForKey:CPViewIsScaledKey] : NO;
|
||||
|
||||
// DOM SETUP
|
||||
#if PLATFORM(DOM)
|
||||
_cssStylePreviousState = @[];
|
||||
|
||||
_DOMImageParts = [];
|
||||
_DOMImageSizes = [];
|
||||
|
||||
@@ -3701,6 +3849,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
#endif
|
||||
|
||||
[self setHidden:[aCoder decodeBoolForKey:CPViewIsHiddenKey]];
|
||||
_isHiddenOrHasHiddenAncestor = NO;
|
||||
|
||||
if ([aCoder containsValueForKey:CPViewOpacityKey])
|
||||
[self setAlphaValue:[aCoder decodeIntForKey:CPViewOpacityKey]];
|
||||
@@ -3709,10 +3858,12 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
|
||||
[self setBackgroundColor:[aCoder decodeObjectForKey:CPViewBackgroundColorKey]];
|
||||
[self _setupViewFlags];
|
||||
[self _decodeThemeObjectsWithCoder:aCoder];
|
||||
|
||||
[self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]];
|
||||
// Set the current appearance to something that can't be the correct one so it will recalculate it at the first layout.
|
||||
_currentAppearance = _frame;
|
||||
|
||||
[self updateTrackingAreas];
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@@ -3735,7 +3886,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
[aCoder encodeRect:_bounds forKey:CPViewBoundsKey];
|
||||
|
||||
// This will come out nil on the other side with decodeObjectForKey:
|
||||
if (_window !== nil)
|
||||
if (_window != nil)
|
||||
[aCoder encodeConditionalObject:_window forKey:CPViewWindowKey];
|
||||
|
||||
var count = [_subviews count],
|
||||
@@ -3754,7 +3905,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
[aCoder encodeObject:encodedSubviews forKey:CPViewSubviewsKey];
|
||||
|
||||
// This will come out nil on the other side with decodeObjectForKey:
|
||||
if (_superview !== nil)
|
||||
if (_superview != nil)
|
||||
[aCoder encodeConditionalObject:_superview forKey:CPViewSuperviewKey];
|
||||
|
||||
if (_autoresizingMask !== CPViewNotSizable)
|
||||
@@ -3763,7 +3914,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
if (!_autoresizesSubviews)
|
||||
[aCoder encodeBool:_autoresizesSubviews forKey:CPViewAutoresizesSubviewsKey];
|
||||
|
||||
if (_backgroundColor !== nil)
|
||||
if (_backgroundColor != nil)
|
||||
[aCoder encodeObject:_backgroundColor forKey:CPViewBackgroundColorKey];
|
||||
|
||||
if (_hitTests !== YES)
|
||||
@@ -3780,12 +3931,12 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
|
||||
var nextKeyView = [self nextKeyView];
|
||||
|
||||
if (nextKeyView !== nil && ![nextKeyView isEqual:self])
|
||||
if (nextKeyView != nil && ![nextKeyView isEqual:self])
|
||||
[aCoder encodeConditionalObject:nextKeyView forKey:CPViewNextKeyViewKey];
|
||||
|
||||
var previousKeyView = [self previousKeyView];
|
||||
|
||||
if (previousKeyView !== nil && ![previousKeyView isEqual:self])
|
||||
if (previousKeyView != nil && ![previousKeyView isEqual:self])
|
||||
[aCoder encodeConditionalObject:previousKeyView forKey:CPViewPreviousKeyViewKey];
|
||||
|
||||
[self _encodeThemeObjectsWithCoder:aCoder];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user